diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..65fc8d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +wiki/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f1797eb --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,199 @@ +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # +# Copyright (c) 2019, De Graef Group, Carnegie Mellon University # +# All rights reserved. # +# # +# Author: William C. Lenthe # +# # +# This package is free software; you can redistribute it and/or # +# modify it under the terms of the GNU General Public License as # +# published by the Free Software Foundation; either version 2 of the # +# License, or (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program; if not, check the Free Software Foundation # +# website: # +# # +# # +# Interested in a commercial license? Contact: # +# # +# Center for Technology Transfer and Enterprise Creation # +# 4615 Forbes Avenue, Suite 302 # +# Pittsburgh, PA 15213 # +# # +# phone. : 412.268.7393 # +# email : innovation@cmu.edu # +# website: https://www.cmu.edu/cttec/ # +# # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +project(EMSphInx) +# cmake_minimum_required(VERSION 3.12) # 3.12 for the parallel option for cmake --build +cmake_minimum_required(VERSION 3.14) # for FetchContent_MakeAvailable (could fall back to 3.12 with alternate pattern but it is now deprecated) + +set(CMAKE_CXX_STANDARD 11) # apparently you still need to explicitly request 2011 conformance in 2019... + +################################ +# package options # +################################ + +option(EMSPHINX_BUILD_SHARED "should shared or static libraries be preferred" OFF) +option(EMSPHINX_FFTW_F "enable float templates by linking against fftw_f" OFF) +option(EMSPHINX_FFTW_D "enable double templates by linking against fftw" ON ) +option(EMSPHINX_FFTW_L "enable long double templates by linking against fftw_l" OFF) +option(EMSPHINX_BUILD_FFTW "fetch and build FFTW instead of using an existing system installation" ON ) +option(EMSPHINX_FFTW_SIMD "should SSE/SSE2/AVX instructions be enabled for fftw build (faster run, slower compiler)" ON ) +option(EMSPHINX_FFTW_AVX2 "should AVX2 instructions be enabled for fftw build (not supported on all processors) " OFF) +option(EMSPHINX_BUILD_HDF5 "fetch and build HDF5 instead of using an existing system installation" ON ) +option(EMSPHINX_BUILD_TESTS "should test programs (/test/*) be built" ON ) +option(EMSPHINX_BUILD_GUIS "should the GUI programs be built (experimental)" OFF) +if(EMSPHINX_BUILD_GUIS) + option(EMSPHINX_BUILD_wxWidgets "should wxWidgets be built from source" ON) +endif() + +################################ +# multicore compiling # +################################ + +include(ProcessorCount) +ProcessorCount(NCORES) +if(NOT NCORES EQUAL 0) + set(CMAKE_BUILD_PARALLEL_LEVEL NCORES) +endif() + +################################ +# compiler specific adjustment # +################################ + +# visual studio +if(MSVC) + add_definitions(-D_SCL_SECURE_NO_WARNINGS) # these warnings are annoying and not critical + add_definitions(-D_CRT_SECURE_NO_WARNINGS) # these warnings are annoying and not critical + # add_compile_options($<$:/MP>) # use multi core compiling + add_compile_options(/MP) # the generator expression version breaks wxWidgets +endif() + +# gcc +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-pthread" )# std::thread requires pthreads on gcc +endif() + +# non clang osx +# if(APPLE AND NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") # only for gcc, not clang + # set(CMAKE_INSTALL_RPATH_USE_LINK_PATH OFF) # there seems to be an rpath issue on apple + GCC +# endif() +set(MACOSX_RPATH ON) # still haven't fully resolved this... + +################################ +# dynamic library copying # +################################ + +# get prefix/extension for built libraries +if(EMSPHINX_BUILD_SHARED) + set(LIB_PRE ${CMAKE_SHARED_LIBRARY_PREFIX}) + set(LIB_EXT ${CMAKE_SHARED_LIBRARY_SUFFIX}) +else() + set(LIB_PRE ${CMAKE_STATIC_LIBRARY_PREFIX}) + set(LIB_EXT ${CMAKE_STATIC_LIBRARY_SUFFIX}) +endif() + +# determine name of files to link against (windows makes .dll for runtime but also keeps .lib for linking) and copy into build directory +if(MSVC) + set(LNK_EXT ${CMAKE_STATIC_LIBRARY_SUFFIX}) # link against the .lib + set(CPY_DIR bin) # location to copy shared libraries from +else() + set(LNK_EXT ${LIB_EXT}) # link against static or dynamic as appropriate + set(CPY_DIR lib) # location to copy shared libraries from +endif() + +################################ +# git versioning # +################################ + +find_package(Git REQUIRED) +execute_process( # get branch name + COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE EMSPHINX_GIT_BRANCH + OUTPUT_STRIP_TRAILING_WHITESPACE +) +execute_process( # get abbreviated hash + COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE EMSPHINX_GIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +if("${EMSPHINX_GIT_BRANCH}" STREQUAL "") + message(FATAL_ERROR "failed to determine git branch)") +endif() +if("${EMSPHINX_GIT_HASH}" STREQUAL "") + message(FATAL_ERROR "failed to determine git hash)") +endif() + +add_definitions("-DEMSPHINX_GIT_HASH=${EMSPHINX_GIT_HASH}") +add_definitions("-DEMSPHINX_GIT_BRANCH=${EMSPHINX_GIT_BRANCH}") + +################################ +# dependencies # +################################ + +# fetch SHT file format content +# this could be done with a git submodule but the user would have to call: +# git submodule update +# git clone --recursive +# which is a bit annoying +include(FetchContent) +FetchContent_Declare( + SHTfile + GIT_REPOSITORY "https://github.com/EMsoft-org/SHTfile" + GIT_TAG "cfd13df" + # GIT_TAG "v3.1.2" # just get the most recent version for now + # GIT_PROGRESS TRUE # its currently only 1 file, we probably don't need to print out progress +) +# set(FETCHCONTENT_QUIET NO) # again only 1 file +FetchContent_MakeAvailable(SHTfile) +FetchContent_GetProperties(SHTfile BINARY_DIR SHTfile_BINARY_DIR) +include_directories(${SHTfile_BINARY_DIR}) + +# compiled dependencies +include(${CMAKE_CURRENT_LIST_DIR}/depends/FFTW.cmake) # build or find existing FFTW +include(${CMAKE_CURRENT_LIST_DIR}/depends/HDF5.cmake) # build or find existing HDF5 + +if(EMSPHINX_BUILD_GUIS) + include(${CMAKE_CURRENT_LIST_DIR}/depends/wxWidgets.cmake) # build or find existing HDF5 +endif() + +# png support, for now I've included these files directly due to download issues on some versions of cmake (if libcurl doesn't support https) +set(BuildMiniZ OFF) +if(BuildMiniZ) + set(MINIZ_VERSION "2.0.8") + ExternalProject_Add( + miniz PREFIX "miniz" + URL https://github.com/richgel999/miniz/releases/download/${MINIZ_VERSION}/miniz-${MINIZ_VERSION}.zip + URL_HASH MD5=0692c3f080267b24419ab67c1eefc881 # make sure the download wasn't corrupted (/ someone hasn't injected a different version) + CONFIGURE_COMMAND "" ${CMAKE_COMMAND} -E copy_directory ${CMAKE_BINARY_DIR}/miniz/src/miniz/ ${CMAKE_SOURCE_DIR}/include/miniz # copy everything (source + license are most critical) + BUILD_COMMAND "" INSTALL_COMMAND "" + ) +endif(BuildMiniZ) + +################################ +# add actual components # +################################ + +# include headers +include_directories(${CMAKE_CURRENT_LIST_DIR}/include) +include_directories(${CMAKE_CURRENT_LIST_DIR}/icons) + +# add test programs if needed +if(EMSPHINX_BUILD_TESTS) + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/test ${CMAKE_BINARY_DIR}/test) # don't put in /cpp/test +endif() + +# add actual programs +include(${CMAKE_CURRENT_LIST_DIR}/programs/CMakeLists.txt) # include instead of add_subdirectory so we don't get a subfolder in the build diff --git a/ReadMe.md b/ReadMe.md new file mode 100644 index 0000000..5fcf042 --- /dev/null +++ b/ReadMe.md @@ -0,0 +1,74 @@ +![EMSphInx Logo](icons/sphinx.png) + +# EMSphInx +*EMSphInx* is a collection of modules and programs that can be used to index a variety of diffraction patterns, ranging from EBSD to TKD, ECP, and x-ray Laue patterns. EMSphInx is currently a public beta; please report bugs to the issue tracker. If you use these programs for research, consider citing the corresponding papers: + +* [EBSD Indexing](https://doi.org/10.1016/j.ultramic.2019.112841) +* [Pseudo-symmetry Prediction](https://doi.org/10.1107/S1600576719011233) + +## Financial Support +The *EMSphInx* code was developed with support from an ONR Vannevar Bush Faculty Fellowship grant, N00014-­16-­1-­2821. The central indexing algorithm is covered by a provisional patent application. + +## Build Instructions +Nightly builds will be available soon for a variety of operating systems + +*EMSphInx* requires [CMake 3.14 or higher](https://www.cmake.org/download) to build. All dependencies are downloaded and compiled as part of the build process by default. The easiest way to build a non-default version of *EMSphInx* is with the cmake gui or ccmake. If you are restricted to the command line and only need the default configuration you can build with the following sequence: + +Download the source + +> git clone https://github.com/EMsoft-org/EMSphInx + +Create a build directory and move into it + +> mkdir EMSphInxBuild + +> cd EMSphInxBuild + +Run cmake and build, if you would like to build the GUIs you can optionally set the GUI CMake flag (e.g. -DEMSPHINX_BUILD_GUIS=ON) + +> cmake ../EMSphInx + +> make -j + +FFTW can compile SIMD instructions on some platforms even if they are not available on the current hardware. If you encounter illegal instructions at runtime try compiling with SIMD disabled (-DEMSPHINX_FFTW_SIMD=OFF). AVX2 instructions are disabled by default but can be enabled with EMSPHINX_FFTW_AVX2. + +## Utility Program Overview + +1. IndexEBSD + + index EBSD patterns on the command line with a namelist file + +2. MasterXcorr + + compute spherical cross correlation between 2 spherical master patterns + +3. ShtWisdom + + build FFTW wisdom on new systems (reduces initialization time on first execution of other programs) + +4. mp2sht + + convert from EMsoft EBSD master patterns to the new SHT file format used by the indexing programs + +5. EMSphInxEBSD (only if EMSPHINX_BUILD_GUIS=ON) + + graphical user interface to build namelist files for IndexEBSD and/or index patterns directly + +Help files for these programs are available as wiki pages on [github.com:EMsoft-org/EMSphInx/wiki]() or in the documentation folder of this repository. + +## New features in 0.1 +- Public Beta + +## What's coming in future versions +- Additional diffraction modalities +- Python bindings + +Feel free to request additional features using the repo's [issue tracker](https://github.com/EMsoft-org/EMSphInx/issues) (please be sure to tag your issue with the 'enhancement flag') + +## License ## + +*EMSphInx* source files are distributed under GNU General Public License v2.0 (GPL2), see the license.txt file for details. + +*EMSphInx* also includes several files from BSD licensed (3-clause) projects (please refer to the individual files for details): + +- include/miniz/miniz.c diff --git a/data/Nickel.sht b/data/Nickel.sht new file mode 100644 index 0000000..d638d9a Binary files /dev/null and b/data/Nickel.sht differ diff --git a/depends/FFTW.cmake b/depends/FFTW.cmake new file mode 100644 index 0000000..59c79e6 --- /dev/null +++ b/depends/FFTW.cmake @@ -0,0 +1,192 @@ +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # +# Copyright (c) 2019, De Graef Group, Carnegie Mellon University # +# All rights reserved. # +# # +# Author: William C. Lenthe # +# # +# This package is free software; you can redistribute it and/or # +# modify it under the terms of the GNU General Public License as # +# published by the Free Software Foundation; either version 2 of the # +# License, or (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program; if not, check the Free Software Foundation # +# website: # +# # +# # +# Interested in a commercial license? Contact: # +# # +# Center for Technology Transfer and Enterprise Creation # +# 4615 Forbes Avenue, Suite 302 # +# Pittsburgh, PA 15213 # +# # +# phone. : 412.268.7393 # +# email : innovation@cmu.edu # +# website: https://www.cmu.edu/cttec/ # +# # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +################################################ +# # +# FFTW # +# # +################################################ + +# apply switches for which versions of fftw to use +if(NOT (${EMSPHINX_FFTW_F} OR ${EMSPHINX_FFTW_D} OR ${EMSPHINX_FFTW_L}) ) + message(FATAL_ERROR "must link against at least one of fftw_f, fftw, or fftw_l (set EMSPHINX_FFTW_{F,D, or L} to ON") +endif() + +# define preprocessor macros to define function that use float/double/long double versions of fftw +if(${EMSPHINX_FFTW_F}) + add_definitions(-DEM_USE_F) # if cmake is 3.11+ should use add_compile_definitions(EM_USE_F) +endif() +if(${EMSPHINX_FFTW_D}) + add_definitions(-DEM_USE_D) # if cmake is 3.11+ should use add_compile_definitions(EM_USE_D) +endif() +if(${EMSPHINX_FFTW_L}) + add_definitions(-DEM_USE_L) # if cmake is 3.11+ should use add_compile_definitions(EM_USE_L) +endif() + +set(FFTW_LIBRARIES "") # store all fftw libraries to link against here +set(FFTW_DEPENDS "") # store all fftw dependencies here + +if(EMSPHINX_BUILD_FFTW) # download + build fftw + # version of fftw to build, any version 3+ should be fine + set(FFTW_VER "3.3.7") + + # name to use for projects (folder name for build + internal cmake variable name) + set(FFTW_NAME_F fftwf) + set(FFTW_NAME_D fftwd) + set(FFTW_NAME_L fftwl) + + # URL to fetch FFTW source from + set(FFTW_URL "http://www.fftw.org/fftw-${FFTW_VER}.tar.gz") + + # name of file to save fetched source as + set(FFTW_SAVE "fftw-${FFTW_VER}.tar.gz") + + # common ccmake args for fftw versions + set(FFTW_OPTIONS -DBUILD_TESTS=OFF -DCMAKE_INSTALL_LIBDIR=lib) # force install to /lib/ (fftw defaults to /lib64/ on some systems) + if(EMSPHINX_BUILD_SHARED) + set(FFTW_OPTIONS ${FFTW_OPTIONS} -DBUILD_SHARED_LIBS=ON ) # build options (arguments for ccmake) + else() + set(FFTW_OPTIONS ${FFTW_OPTIONS} -DBUILD_SHARED_LIBS=OFF) # build options (arguments for ccmake) + endif() + + # simd ccmake args for fftw versions (only float + double) + if(EMSPHINX_FFTW_SIMD) + if(EMSPHINX_FFTW_AVX2) + set(FFTW_SIMD -DENABLE_SSE=ON -DENABLE_SSE2=ON -DENABLE_AVX=ON -DENABLE_AVX2=ON ) # flags to enable SIMD instructions for fftw + else() + set(FFTW_SIMD -DENABLE_SSE=ON -DENABLE_SSE2=ON -DENABLE_AVX=ON -DENABLE_AVX2=OFF) # flags to enable SIMD instructions for fftw + endif() + else() + set(FFTW_SIMD "") # flags to enable SIMD instructions for fftw + endif() + + # location to install builds + set(FFTW_BUILD_DIR_F ${CMAKE_CURRENT_BINARY_DIR}/${FFTW_NAME_F}) + set(FFTW_BUILD_DIR_D ${CMAKE_CURRENT_BINARY_DIR}/${FFTW_NAME_D}) + set(FFTW_BUILD_DIR_L ${CMAKE_CURRENT_BINARY_DIR}/${FFTW_NAME_L}) + + # now set up each version of fftw required + include(ExternalProject) + + # build float fftw if needed + if(${EMSPHINX_FFTW_F}) + ExternalProject_add(${FFTW_NAME_F} PREFIX ${FFTW_NAME_F} URL ${FFTW_URL} DOWNLOAD_NAME ${FFTW_SAVE} + CMAKE_ARGS ${FFTW_OPTIONS} ${FFTW_SIMD} -DCMAKE_INSTALL_PREFIX=${FFTW_BUILD_DIR_F}/install -DENABLE_FLOAT=ON # agruments for ccmake + BUILD_COMMAND ${CMAKE_COMMAND} --build . --parallel ${NCORES} + ) + if(EMSPHINX_BUILD_SHARED) # copy shared library to binary directory if needed + ExternalProject_Add_Step(${FFTW_NAME_F} CopyLibs # execute copy command as part of build + COMMAND ${CMAKE_COMMAND} -E copy_directory ${FFTW_BUILD_DIR_F}/install/${CPY_DIR}/ ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "copying fftwf binarys to build folder" + DEPENDEES install # don't copy from install dir until files have been copied into it + ) + endif() + endif(${EMSPHINX_FFTW_F}) + + # build double fftw if needed + if(${EMSPHINX_FFTW_D}) + ExternalProject_add(${FFTW_NAME_D} PREFIX ${FFTW_NAME_D} URL ${FFTW_URL} DOWNLOAD_NAME ${FFTW_SAVE} + CMAKE_ARGS ${FFTW_OPTIONS} ${FFTW_SIMD} -DCMAKE_INSTALL_PREFIX=${FFTW_BUILD_DIR_D}/install # agruments for ccmake + BUILD_COMMAND ${CMAKE_COMMAND} --build . --parallel ${NCORES} + ) + if(EMSPHINX_BUILD_SHARED) # copy shared library to binary directory if needed + ExternalProject_Add_Step(${FFTW_NAME_D} CopyLibs # execute copy command as part of build + COMMAND ${CMAKE_COMMAND} -E copy_directory ${FFTW_BUILD_DIR_D}/install/${CPY_DIR}/ ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "copying fftw binarys to build folder" + DEPENDEES install # don't copy from install dir until files have been copied into it + ) + endif() + endif(${EMSPHINX_FFTW_D}) + + # build long double fftw if needed + if(${EMSPHINX_FFTW_L}) + ExternalProject_add(${FFTW_NAME_L} PREFIX ${FFTW_NAME_L} URL ${FFTW_URL} DOWNLOAD_NAME ${FFTW_SAVE} + CMAKE_ARGS ${FFTW_OPTIONS} -DCMAKE_INSTALL_PREFIX=${FFTW_BUILD_DIR_L}/install -DENABLE_LONG_DOUBLE=ON # agruments for ccmake + BUILD_COMMAND ${CMAKE_COMMAND} --build . --parallel ${NCORES} + ) + if(EMSPHINX_BUILD_SHARED) # copy shared library to binary directory if needed + ExternalProject_Add_Step(${FFTW_NAME_L} CopyLibs # execute copy command as part of build + COMMAND ${CMAKE_COMMAND} -E copy_directory ${FFTW_BUILD_DIR_L}/install/${CPY_DIR}/ ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "copying fftwl binarys to build folder" + DEPENDEES install # don't copy from install dir until files have been copied into it + ) + endif() + endif(${EMSPHINX_FFTW_L}) + + # include fftw3.h once + if(${EMSPHINX_FFTW_F}) + include_directories(${FFTW_BUILD_DIR_F}/install/include) + elseif(${EMSPHINX_FFTW_D}) + include_directories(${FFTW_BUILD_DIR_D}/install/include) + elseif(${EMSPHINX_FFTW_L}) + include_directories(${FFTW_BUILD_DIR_L}/install/include) + endif() + + # build list of fftw libraries / projects + if(${EMSPHINX_FFTW_F}) + list(APPEND FFTW_LIBRARIES ${FFTW_BUILD_DIR_F}/install/lib/${LIB_PRE}fftw3f${LNK_EXT}) # add built library to list for linking + list(APPEND FFTW_DEPENDS fftwf) # accumulate dependencies + endif() + if(${EMSPHINX_FFTW_D}) + list(APPEND FFTW_LIBRARIES ${FFTW_BUILD_DIR_D}/install/lib/${LIB_PRE}fftw3${LNK_EXT} ) # add built library to list for linking + list(APPEND FFTW_DEPENDS fftwd) # accumulate dependencies + endif() + if(${EMSPHINX_FFTW_L}) + list(APPEND FFTW_LIBRARIES ${FFTW_BUILD_DIR_L}/install/lib/${LIB_PRE}fftw3l${LNK_EXT}) # add built library to list for linking + list(APPEND FFTW_DEPENDS fftwl) # accumulate dependencies + endif() +else(EMSPHINX_BUILD_FFTW) # use existing fftw builds + # find float libraries if needed + if(${EMSPHINX_FFTW_F}) + find_library(FFTW_LIBRARY_F fftw3f "fftwfloat library") + list(APPEND FFTW_LIBRARIES ${FFTW_LIBRARY_F}) + endif() + + # find double libraries if needed + if(${EMSPHINX_FFTW_D}) + find_library(FFTW_LIBRARY_D fftw3 "fftw double library") + list(APPEND FFTW_LIBRARIES ${FFTW_LIBRARY_D}) + endif() + + # find long double libraries if needed + if(${EMSPHINX_FFTW_L}) + find_library(FFTW_LIBRARY_L fftw3l "fftw long double library") + list(APPEND FFTW_LIBRARIES ${FFTW_LIBRARY_L}) + endif() + + # find the header + find_file(FFTW_HEADER fftw.h "fftw header") + get_filename_component(FFTW_INCLUDE ${FFTW_HEADER} DIRECTORY) + include_directories(${FFTW_INCLUDE}) +endif(EMSPHINX_BUILD_FFTW) diff --git a/depends/HDF5.cmake b/depends/HDF5.cmake new file mode 100644 index 0000000..8592794 --- /dev/null +++ b/depends/HDF5.cmake @@ -0,0 +1,119 @@ +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # +# Copyright (c) 2019, De Graef Group, Carnegie Mellon University # +# All rights reserved. # +# # +# Author: William C. Lenthe # +# # +# This package is free software; you can redistribute it and/or # +# modify it under the terms of the GNU General Public License as # +# published by the Free Software Foundation; either version 2 of the # +# License, or (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program; if not, check the Free Software Foundation # +# website: # +# # +# # +# Interested in a commercial license? Contact: # +# # +# Center for Technology Transfer and Enterprise Creation # +# 4615 Forbes Avenue, Suite 302 # +# Pittsburgh, PA 15213 # +# # +# phone. : 412.268.7393 # +# email : innovation@cmu.edu # +# website: https://www.cmu.edu/cttec/ # +# # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +################################################ +# # +# HDF5 # +# # +################################################ + +set(HDF5_LIBRARIES "") # store all hdf5 libraries to link against here + +if(EMSPHINX_BUILD_HDF5) + set(HDF5_VERS "hdf5-1_8_20") # version of hdf5 to build, 1_10_x requires cmake 3.10 or newer + set(HDF5_URL "https://bitbucket.hdfgroup.org/scm/hdffv/hdf5.git") # git repo to fetch hdf5 source from + set(HDF5_OPTIONS -DBUILD_TESTING=OFF -DHDF5_BUILD_EXAMPLES=OFF -DHDF5_BUILD_TOOLS=OFF -DHDF5_DISABLE_COMPILER_WARNINGS=ON -DHDF5_BUILD_HL_LIB=OFF) # build options (arguments for ccmake) + set(HDF5_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/hdf5) # location to build + + if(EMSPHINX_BUILD_SHARED) + set(HDF5_OPTIONS ${HDF5_OPTIONS} -DBUILD_SHARED_LIBS=ON ) # build options (arguments for ccmake) + else() + set(HDF5_OPTIONS ${HDF5_OPTIONS} -DBUILD_SHARED_LIBS=OFF) # build options (arguments for ccmake) + endif() + + # now set up hdf5 build + include(ExternalProject) + ExternalProject_add(hdf5 PREFIX hdf5 GIT_REPOSITORY ${HDF5_URL} GIT_TAG ${HDF5_VERS} + # UPDATE_DISCONNECTED 1 # this should keep the cmake from trying to repull from the repo every time make is run but doesn't... + UPDATE_COMMAND "" # this does keep cmake from redoing everythin on every make but means that if you change HDF5_VERS it won't automatically pull and build the new one + CMAKE_ARGS ${HDF5_OPTIONS} -DCMAKE_INSTALL_PREFIX=${HDF5_BUILD_DIR}/install + BUILD_COMMAND ${CMAKE_COMMAND} --build . --parallel ${NCORES} + ) + if(EMSPHINX_BUILD_SHARED) # copy shared library to binary directory if needed + ExternalProject_Add_Step(hdf5 CopyLibs # execute copy command as part of build + COMMAND ${CMAKE_COMMAND} -E copy_directory ${HDF5_BUILD_DIR}/install/lib/ ${CMAKE_CURRENT_BINARY_DIR} # hdf5 seems to use /lib/ even when /lib64/ is preferred + COMMENT "copying HDF5 binarys to build folder" + DEPENDEES install # don't copy from install dir until files have been copied into it + ) + endif() + + # hdf5 names librarys [lib]hdf5_x[-static].(a/lib) or [lib]hdf5_x[-shared].(so/dll) + if(MSVC) + set(HDF5_PRE "lib") # windows always uses libhdf5(_x).lib + set(HDF5_POST "" ) # windows doesn't use a library suffix for hdf5 + set(HDF5_DEB "_D" ) # windows appends debug library names with _D + else() + set(HDF5_PRE ${LIB_PRE}) + if(EMSPHINX_BUILD_SHARED) + set(HDF5_POST "-shared") + else() + set(HDF5_POST "-static") + endif() + set(HDF5_DEB "") + endif() + + # finally accumulate hdf5 libraries and headers + include_directories(${HDF5_BUILD_DIR}/install/include) + set(HDF5_NAMES hdf5_cpp hdf5) # hdf5_hl hdf5_hl_cpp + # set(HDF5_NAMES hdf5 hdf5_hl hdf5_cpp hdf5_hl_cpp) + foreach(HDF5_LIB ${HDF5_NAMES}) + list(APPEND HDF5_LIBRARIES ${HDF5_BUILD_DIR}/install/lib/${HDF5_PRE}${HDF5_LIB}${HDF5_POST}$<$:${HDF5_DEB}>${LNK_EXT}) # hdf5 seems to use /lib/ even when /lib64/ is preferred + endforeach() + + if(UNIX AND NOT APPLE) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -ldl") + endif() + +else(EMSPHINX_BUILD_HDF5) + find_package(HDF5 REQUIRED COMPONENTS CXX PATHS "${HDF5_ROOT}") # search for HDF5 + if(HDF5_FOUND) # hdf5 was found automatically + include_directories(${HDF5_INCLUDE_DIR}) # include hdf5 headers + + #should be able to just use find package but it seems to be broken so I'll manually find libraries instead + if("${HDF5_LIBRARIES}" STREQUAL "") # did find_package actually get HDF5_LIBRARIES? + get_filename_component(HDF5_ROOT "${HDF5_INCLUDE_DIR}" DIRECTORY CACHE PATH) # parent of include directory + find_library(HDF5_C_LIBRARIES hdf5-shared PATH "${HDF5_ROOT}/lib") # prefer shared libraries + find_library(HDF5_CXX_LIBRARIES hdf5_cpp-shared PATH "${HDF5_ROOT}/lib") # prefer shared libraries + if(NOT ${HDF5_C_LIBRARIES}) + find_library(HDF5_C_LIBRARIES hdf5-static PATH "${HDF5_ROOT}/lib") # fall back to static libraries + endif() + if(NOT ${HDF5_CXX_LIBRARIES}) + find_library(HDF5_CXX_LIBRARIES hdf5_cpp-static PATH "${HDF5_ROOT}/lib") # fall back to static libraries + endif() + set(HDF5_LIBRARIES ${HDF5_C_LIBRARIES} ${HDF5_CXX_LIBRARIES}) + endif() + else(HDF5_FOUND) # hdf5 wasn't found automatically + message(FATAL_ERROR "This project requires HDF5 configured with CMake, try setting the HDF5_ROOT variable") + endif(HDF5_FOUND) +endif(EMSPHINX_BUILD_HDF5) diff --git a/depends/wxWidgets.cmake b/depends/wxWidgets.cmake new file mode 100644 index 0000000..c00c19b --- /dev/null +++ b/depends/wxWidgets.cmake @@ -0,0 +1,77 @@ +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # +# Copyright (c) 2019, De Graef Group, Carnegie Mellon University # +# All rights reserved. # +# # +# Author: William C. Lenthe # +# # +# This package is free software; you can redistribute it and/or # +# modify it under the terms of the GNU General Public License as # +# published by the Free Software Foundation; either version 2 of the # +# License, or (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program; if not, check the Free Software Foundation # +# website: # +# # +# # +# Interested in a commercial license? Contact: # +# # +# Center for Technology Transfer and Enterprise Creation # +# 4615 Forbes Avenue, Suite 302 # +# Pittsburgh, PA 15213 # +# # +# phone. : 412.268.7393 # +# email : innovation@cmu.edu # +# website: https://www.cmu.edu/cttec/ # +# # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +################################################ +# # +# wxWidgets # +# # +################################################ + + +# set(wxWidgets_LIBRARIES "") # store all wxWidgets libraries to link against here + +if(EMSPHINX_BUILD_wxWidgets) + + include(FetchContent) + FetchContent_Declare( + wxWidgets + GIT_REPOSITORY "https://github.com/wxWidgets/wxWidgets" + GIT_TAG "v3.1.2" + GIT_PROGRESS TRUE + GIT_SHALLOW TRUE + ) + set(FETCHCONTENT_QUIET NO) + if(NOT EMSPHINX_BUILD_SHARED) + set(wxBUILD_SHARED OFF CACHE BOOL "Build wx libraries as shared libs") + endif() + + # set(wxBUILD_PRECOMP OFF) # this prevents errors on recompile after the git hash changes + if(UNIX AND NOT APPLE) + message (WARNING "wxWidgets needs gtk3, make sure you have it with e.g. 'apt-get install libgtk-3-dev build-essential'") + set(wxBUILD_TOOLKIT gtk3 CACHE STRING "Toolkit used by wxWidgets") + endif() + + if(APPLE) + set(wxUSE_WEBKIT OFF CACHE BOOL "use wxWebKitCtrl (Mac-only, use wxWebView instead)") + set(wxUSE_WEBVIEW_WEBKIT OFF CACHE BOOL "use wxWebView WebKit background") + endif() + + FetchContent_MakeAvailable(wxWidgets) + +else(EMSPHINX_BUILD_wxWidgets) + # SET(wxWidgets_ROOT_DIR ) + # SET(wxWidgets_CONFIGURATION mswud) + find_package(WxWidgets COMPONENTS core base REQUIRED) + include(${wxWidgets_USE_FILE}) +endif(EMSPHINX_BUILD_wxWidgets) diff --git a/documentation/tutorial_images/IndexParams.png b/documentation/tutorial_images/IndexParams.png new file mode 100644 index 0000000..8844586 Binary files /dev/null and b/documentation/tutorial_images/IndexParams.png differ diff --git a/documentation/tutorial_images/IndexingView.png b/documentation/tutorial_images/IndexingView.png new file mode 100644 index 0000000..1c16511 Binary files /dev/null and b/documentation/tutorial_images/IndexingView.png differ diff --git a/documentation/tutorial_images/MasterPatternFilt.png b/documentation/tutorial_images/MasterPatternFilt.png new file mode 100644 index 0000000..1157b33 Binary files /dev/null and b/documentation/tutorial_images/MasterPatternFilt.png differ diff --git a/documentation/tutorial_images/MasterPatternSelect.png b/documentation/tutorial_images/MasterPatternSelect.png new file mode 100644 index 0000000..d8ae08d Binary files /dev/null and b/documentation/tutorial_images/MasterPatternSelect.png differ diff --git a/documentation/tutorial_images/PatternCenter.png b/documentation/tutorial_images/PatternCenter.png new file mode 100644 index 0000000..73d6d4d Binary files /dev/null and b/documentation/tutorial_images/PatternCenter.png differ diff --git a/documentation/tutorial_images/PatternLoadInfo.png b/documentation/tutorial_images/PatternLoadInfo.png new file mode 100644 index 0000000..954fa7d Binary files /dev/null and b/documentation/tutorial_images/PatternLoadInfo.png differ diff --git a/documentation/tutorial_images/PatternLoadProc.png b/documentation/tutorial_images/PatternLoadProc.png new file mode 100644 index 0000000..e46e4a3 Binary files /dev/null and b/documentation/tutorial_images/PatternLoadProc.png differ diff --git a/documentation/tutorial_images/SummaryPanel.png b/documentation/tutorial_images/SummaryPanel.png new file mode 100644 index 0000000..5acd1bf Binary files /dev/null and b/documentation/tutorial_images/SummaryPanel.png differ diff --git a/documentation/tutorial_images/ipf.png b/documentation/tutorial_images/ipf.png new file mode 100644 index 0000000..299dc51 Binary files /dev/null and b/documentation/tutorial_images/ipf.png differ diff --git a/documentation/tutorial_images/roi.png b/documentation/tutorial_images/roi.png new file mode 100644 index 0000000..389d409 Binary files /dev/null and b/documentation/tutorial_images/roi.png differ diff --git a/documentation/wiki.md b/documentation/wiki.md new file mode 100644 index 0000000..d928b5e --- /dev/null +++ b/documentation/wiki.md @@ -0,0 +1,114 @@ + +# EMSphInxEBSD wizard + +The EBSD namelist generation wizard *EMSphInxEBSD.app* has 6 panels: + +1. Experimental Pattern Selection +2. Master Pattern Selection +3. Detector Geometry +4. Scan Geometry +5. Indexing Parameters +6. Summary + +Any error messages are displayed in the status bar (bottom left) + +## Experimental Pattern Selection + +![PatternLoadInfo.png](tutorial_images/PatternLoadInfo.PNG) + + 1. Load a pattern file with the "..."" button); the pattern dimensions, pixel type, and number of patterns will be displayed in the *Pattern Info* box. Additional scan information will be loaded if the pattern file is + * an HDF5 file with an EBSD scan (standard across vendors) + * a *.up1 or *.up2 file accompanied by an *.ang file with the same name + * an *.ebsp file accomanied by a *.ctf file with the same name + 2. Select a number of patterns to load (evenly spaced) and configure patterns with the *Preview...* button. + * Mask Radius - mask for circular detectors + * -1 - no circular mask + * 0 - largest inscribed circle + * >0 - specify circular mask radius in pixels + * Gaussian Background - fit a 2D Gaussian background to each pattern + * Histogram Equalization - number of adaptive histogram tiles + * 0 - no histogram equalization + * 1 - regular (not adaptive) histogram equalization + * >1 - number of AHE tiles + +![PatternLoadProc.png](tutorial_images/PatternLoadProc.PNG) + + + +## Master Pattern Selection + + +Patterns to be indexed are shown in the top half with available patterns in the bottom half. Double click master patterns to move them between the 2 boxes (or [un]tick the checkbox on OS X and Linux) Use the up/down arrows to re-order patterns for multi phase indexing (choose phase 0, 1, 2, ...). The file browse button (center right) adds individual master patterns to the indexing list and the folder browse button (bottom right) adds all master pattern files in the selected directory (recursively) to the library. Use the delete button (bottom left) to remove patterns from the library and the search button (bottom left) to filter the library display. The search bar filters files using the file name (full path, case insensitive). + +![MasterPatternFilt.png](tutorial_images/MasterPatternFilt.PNG) + + +## Detector Geometry + + 1. Update the binning factor used to collect the experimental patterns, the corresponding unbinned detector size (in pixels) will be shown. + 2. Specify the pixel size for the current binning factor (generally ~60 um) or the full size of the detector (generally ~30 mm). + 3. Input the pattern center using the selected convention (or verify the center loaded from a scan file on the first page). + 4. Optionally refine the pattern center with the *fit...* button (not yet implemented in this version of the wizard). + +![PatternCenter.png](tutorial_images/PatternCenter.PNG) + +## Scan Geometry + + 1. Specify the scan and pixel width/height (or verify values loaded from a scan file). + 2. Optionally select a region of interest using the *Select ROI...* button. The image used for selection is determined by the drop down menu. + +![roi.png](tutorial_images/roi.PNG) + +## Indexing Parameters + + 1. Specify the bandwidth to use for indexing. Computation time scales as bw^3 * ln(bw^3) so choose the smallest tolerable value. For optimal efficiency 2*bw-1 should be a product of small primes. Some reasonable values are {53, 63, 68, 74, 88, 95, 113, 122, 123, 158}, but any size will be zero padded to next product of small primes so all sizes are nearly equivalently efficient in practice. (preview functionality is not yet implemented) + 2. Select normalized or unnormalized cross correlation and if orientation refinement should be applied. + 3. Select output files + * Data File - HDF5 file to write output to (required) + * Vendor File - *.ang or *.ctf output (optional) + * IPF Map - {0,0,1} reference direction (optional) + * CI Map - cross correlation map (optional) + +![IndexParams.png](tutorial_images/IndexParams.PNG) + +## Summary Panel + + 1. A read only summary of selected parameters is shown. Return to previous pages to edit any values if needed. + 2. When you're satisfied with the parameters, click finish: + + * Index Now - index with gui + * Export Namelist - generate a namelist file to use with the command line program + * Cancel - return to summary panel + +![SummaryPanel.png](tutorial_images/SummaryPanel.PNG) + + +# Indexing View + +* Parameters can be edited while not indexing (but are not validated) + +![IndexingView](tutorial_images/IndexingView.PNG) + +# Example Data + +The full 10 scan sequence used in the indexing paper [can be downloaded here](https://kilthub.cmu.edu/ndownloader/files/14503052). The entire sequence is ~600 MB, a smaller file (~80 MB) containing only scan 10 is [also available](http://vbff.materials.cmu.edu/wp-content/uploads/2019/10/Hikari_Scan10.zip). A nickel master pattern corresponding to the scan conditions is in this repo (data/Nickel.sht). + +Reasonable selections to walk through the hikari dataset (only non-default values listed): + + +1. Experimental Pattern Selection + * Pattern File - HikariNiSequence.h5: Scan 10 + * Circular Mask Radius - 0 +2. Master Pattern Selection + * Nickel.sht +3. Detector Geometry + * binning - 1 + * pixel size - 475 +4. Scan Geometry +5. Indexing Parameters + * bandwidth - 53 + * refine - true +6. Summary Panel + * choose index now for your first run, after you're comfortable with the gui you can try generating a namelist and indexing with the command line IndexEBSD program + +![ipf.png](tutorial_images/ipf.png) diff --git a/icons/desktop b/icons/desktop new file mode 100644 index 0000000..b7b0674 --- /dev/null +++ b/icons/desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=EMSphInx +Exec=~/emsphinx_build/EbsdWizard +Icon=~/emsphinx/icons/sphinx.png +Terminal=false +Type=Application \ No newline at end of file diff --git a/icons/sphinx.icns b/icons/sphinx.icns new file mode 100644 index 0000000..475d71d Binary files /dev/null and b/icons/sphinx.icns differ diff --git a/icons/sphinx.ico b/icons/sphinx.ico new file mode 100644 index 0000000..cb16a18 Binary files /dev/null and b/icons/sphinx.ico differ diff --git a/icons/sphinx.png b/icons/sphinx.png new file mode 100644 index 0000000..2d7bfe4 Binary files /dev/null and b/icons/sphinx.png differ diff --git a/icons/sphinx.rc b/icons/sphinx.rc new file mode 100644 index 0000000..4d9eb32 --- /dev/null +++ b/icons/sphinx.rc @@ -0,0 +1,4 @@ +#include +sphinx ICON "sphinx.ico" +ApplicationIcon ICON "sphinx.ico" +ICONPRG ICON "sphinx.ico" \ No newline at end of file diff --git a/icons/sphinx.svg b/icons/sphinx.svg new file mode 100644 index 0000000..60f50c2 --- /dev/null +++ b/icons/sphinx.svg @@ -0,0 +1,1609 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/sphinx.xpm b/icons/sphinx.xpm new file mode 100644 index 0000000..64f28f3 --- /dev/null +++ b/icons/sphinx.xpm @@ -0,0 +1,538 @@ +/* XPM */ + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019, William C. Lenthe * + * All rights reserved. * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +static char const * sphinx_xpm[] = { +"256 256 256 2", +" c None", +". c #000100", +"+ c #090100", +"@ c #030602", +"# c #0E0806", +"$ c #080A06", +"% c #150808", +"& c #19090B", +"* c #1D0904", +"= c #290605", +"- c #0D0F0C", +"; c #450000", +"> c #360603", +", c #141108", +"' c #4B0302", +") c #191202", +"! c #2C0C09", +"~ c #1D1200", +"{ c #121411", +"] c #620000", +"^ c #191514", +"/ c #1D170B", +"( c #161816", +"_ c #191811", +": c #750000", +"< c #800000", +"[ c #231B0B", +"} c #261C04", +"| c #1E1D17", +"1 c #211D13", +"2 c #890001", +"3 c #1D1E1C", +"4 c #900000", +"5 c #9C0000", +"6 c #222320", +"7 c #2D230E", +"8 c #272419", +"9 c #A40200", +"0 c #AD0000", +"a c #252623", +"b c #B80000", +"c c #2D2718", +"d c #342708", +"e c #2B2A23", +"f c #2D2A1F", +"g c #C50000", +"h c #332A14", +"i c #3A2A02", +"j c #2A2B29", +"k c #D00000", +"l c #DA0001", +"m c #DD0000", +"n c #342F24", +"o c #2E302D", +"p c #3A3019", +"q c #3D3013", +"r c #323331", +"s c #3A3324", +"t c #43330E", +"u c #493400", +"v c #3B362B", +"w c #363835", +"x c #4F3903", +"y c #483A16", +"z c #3A3C39", +"A c #533C00", +"B c #413D2C", +"C c #463C2C", +"D c #413D31", +"E c #4E3D14", +"F c #3F403D", +"G c #4C3F23", +"H c #544213", +"I c #494439", +"J c #4D4430", +"K c #444542", +"L c #634500", +"M c #534A36", +"N c #4A4B49", +"O c #614A18", +"P c #574B30", +"Q c #554D42", +"R c #6A5016", +"S c #50524F", +"T c #61512C", +"U c #5B523D", +"V c #6F551B", +"W c #66583C", +"X c #7B5800", +"Y c #585A57", +"Z c #625945", +"` c #6C592D", +" . c #795D1B", +".. c #825E03", +"+. c #6D5F44", +"@. c #5F615E", +"#. c #8C6200", +"$. c #83651D", +"%. c #656764", +"&. c #73664A", +"*. c #7C6635", +"=. c #696A68", +"-. c #826A2E", +";. c #7C6A44", +">. c #886A22", +",. c #736B58", +"'. c #986B00", +"). c #7C6D4F", +"!. c #8D6D1D", +"~. c #6D6F6C", +"{. c #907021", +"]. c #997112", +"^. c #837352", +"/. c #947324", +"(. c #737572", +"_. c #A47400", +":. c #8C753D", +"<. c #98761F", +"[. c #807862", +"}. c #777976", +"|. c #887856", +"1. c #9C7A23", +"2. c #A27A25", +"3. c #7C7E7B", +"4. c #AD7C00", +"5. c #8F7D5C", +"6. c #A07E27", +"7. c #9B7E39", +"8. c #B57D00", +"9. c #A58122", +"0. c #AB801B", +"a. c #AA8024", +"b. c #938260", +"c. c #828481", +"d. c #BB8200", +"e. c #9D8549", +"f. c #978664", +"g. c #AF8529", +"h. c #868885", +"i. c #908871", +"j. c #9A8760", +"k. c #AC882A", +"l. c #B78714", +"m. c #C18702", +"n. c #B3882C", +"o. c #BF8A04", +"p. c #B68A26", +"q. c #8B8D8A", +"r. c #B28C26", +"s. c #B08C38", +"t. c #998E6F", +"u. c #A18D65", +"v. c #C88C00", +"w. c #BA8D2A", +"x. c #B8912C", +"y. c #919390", +"z. c #A5916A", +"A. c #BE912E", +"B. c #A09374", +"C. c #CD9203", +"D. c #9E947C", +"E. c #C99314", +"F. c #BE9629", +"G. c #B09657", +"H. c #C4952A", +"I. c #969895", +"J. c #D39500", +"K. c #C29634", +"L. c #AB976F", +"M. c #A59A7B", +"N. c #C8992E", +"O. c #D89A02", +"P. c #B29B6D", +"Q. c #9C9E9B", +"R. c #B09C74", +"S. c #BA9D54", +"T. c #CB9C31", +"U. c #CF9D20", +"V. c #B49E70", +"W. c #DD9E00", +"X. c #CF9F2C", +"Y. c #E1A100", +"Z. c #B8A274", +"`. c #B0A382", +" + c #E7A000", +".+ c #D3A22F", +"++ c #A3A5A2", +"@+ c #BCA577", +"#+ c #E5A502", +"$+ c #D6A532", +"%+ c #E2A702", +"&+ c #DEA61A", +"*+ c #E9A700", +"=+ c #EEA600", +"-+ c #D9A735", +";+ c #DAA82D", +">+ c #BEAB82", +",+ c #ABADA9", +"'+ c #C2AB7C", +")+ c #E9AB0F", +"!+ c #DEAB30", +"~+ c #D1AC53", +"{+ c #D8AC3F", +"]+ c #F4AB00", +"^+ c #E6AD23", +"/+ c #F6AD06", +"(+ c #C7AF81", +"_+ c #E2AF34", +":+ c #F8AE00", +"<+ c #B0B2AF", +"[+ c #CBB385", +"}+ c #E6B339", +"|+ c #B5B7B4", +"1+ c #CAB58C", +"2+ c #C6B692", +"3+ c #EAB532", +"4+ c #E1B64F", +"5+ c #D0B682", +"6+ c #F2B62C", +"7+ c #EDB835", +"8+ c #B9BBB8", +"9+ c #F4B82E", +"0+ c #D4BA85", +"a+ c #F0BA38", +"b+ c #F2BC3A", +"c+ c #EFBC46", +"d+ c #BDBFBC", +"e+ c #FBBD2A", +"f+ c #D8BE89", +"g+ c #F4BE3C", +"h+ c #F7C035", +"i+ c #C2C4C1", +"j+ c #DCC28D", +"k+ c #FAC238", +"l+ c #FFC139", +"m+ c #FCC43A", +"n+ c #D8C59B", +"o+ c #DFC590", +"p+ c #C6C8C5", +"q+ c #E2C892", +"r+ c #CACCC9", +"s+ c #E5CB95", +"t+ c #EACE92", +"u+ c #E9CE98", +"v+ c #E5CFA5", +"w+ c #EDD195", +"x+ c #EBD19D", +"y+ c #D3D5D2", +"z+ c #EFD396", +"A+ c #F2D599", +"B+ c #F4D89B", +"C+ c #F7DA9D", +"D+ c #DBDDDA", +"E+ c #FCDFA2", +"F+ c #E3E5E2", +"G+ c #ECEEEB", +" ~.=.Y Y N K F z r o e e 3 3 ( - - - - - - @ . . . . . . . . . . @ - - - - - - ( | 3 e e j r z z I N Y Y =.=. ", +" %.S F e 3 { @ . . . . . $ , / 7 h p q E E O O V V V . .$.>.>.!.<.<.1.1.1.<.<.6.9.9.9.9.9.9.9.9.6.<.<.<.<.<.<.<.!.>.>. . . .V V R O O E E q p h [ / , # . . . . . @ - | e F S %. ", +" ~.S r ( . . . $ / 7 y H R .!.<.9.r.H.X.;+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+3+}+3+3+}+}+}+}+}+}+}+}+}+}+}+}+}+.+T.A.k.6./.>. .O H t 7 / $ . . . ^ o N %. ", +" }.N ^ . @ 1 q O ./.r.X._+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+}+}+}+}+}+}+}+}+}+_+_+_+_+-+F.9.!.V H p / @ . { F %. ", +" N $ $ h O /.H.3+a+a+a+a+a+a+7+7+7+3+3+7+7+7+3+3+7+7+3+3+7+7+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+}+}+}+}+}+}+}+}+_+_+_+!+!+!+!+!+;+.+k.$.H 7 $ $ F ", +" %.@ E k.3+g+b+b+b+b+b+a+a+a+a+a+a+a+a+a+7+7+a+a+7+7+a+a+a+7+a+a+a+a+a+a+a+a+7+a+a+a+a+a+a+a+a+a+a+a+a+a+7+7+a+a+7+a+a+a+a+7+7+7+7+7+7+7+7+7+a+a+7+7+3+3+7+7+3+3+3+3+3+3+3+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+}+}+}+}+}+_+!+!+!+!+!+-+-+-+.+.+N.{.q @ S ", +" K , x.h+h+h+g+g+g+b+b+b+b+b+b+b+a+a+a+a+b+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+7+7+a+a+a+a+a+7+7+a+7+7+7+7+7+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+}+}+}+_+_+_+!+!+;+-+-+.+.+.+.+T./., w ", +" K / .+h+h+h+h+h+h+h+h+g+b+b+b+b+b+b+b+b+b+b+b+a+a+b+b+b+b+a+b+b+a+b+b+a+b+b+b+b+a+b+b+a+b+b+b+a+b+b+b+a+b+b+b+b+b+b+a+a+a+b+b+b+b+b+b+b+b+b+b+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+a+7+7+7+7+7+3+3+3+7+3+3+3+3+3+}+}+}+_+_+!+!+-+-+$+.+.+X.T.N.9., w ", +" =., .+h+h+h+h+h+h+h+h+h+h+h+h+g+g+g+g+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+g+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+a+a+b+b+b+a+a+b+a+a+b+a+a+a+a+a+a+a+a+a+a+a+a+a+a+7+a+a+3+3+3+3+3+3+3+}+}+_+!+!+!+;+-+.+.+.+T.N.N.k.- N ", +" $ x.k+k+k+k+k+k+k+k+k+h+h+h+h+h+h+h+h+h+g+g+g+h+g+b+h+b+b+g+g+g+b+b+b+b+g+g+b+b+b+b+g+b+b+g+g+b+b+b+g+g+b+b+b+b+b+b+b+g+g+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+a+b+b+b+b+a+b+b+b+b+b+b+b+a+a+a+b+a+a+a+a+a+a+a+a+a+a+a+a+7+3+3+3+3+3+3+}+_+!+!+!+-+.+.+.+T.N.N.H./.@ ", +" @ 1.k+k+k+k+k+m+m+m+m+m+k+k+k+k+h+h+h+h+h+h+h+h+h+h+h+h+g+h+h+h+h+b+h+g+b+g+h+g+g+g+g+g+b+g+h+g+g+g+g+h+g+b+b+b+b+g+g+b+h+h+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+a+a+a+a+a+a+a+7+3+3+3+3+3+}+_+!+!+;+-+.+.+X.N.N.H.H.$.@ ", +" $ .k+k+k+k+m+m+m+m+m+m+m+m+m+m+k+k+k+h+h+h+h+h+h+h+h+h+h+g+h+h+h+h+b+h+h+b+g+h+h+g+g+g+g+g+g+g+b+g+g+g+g+g+g+b+b+b+g+g+b+g+g+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+a+b+a+a+a+a+a+7+3+3+3+3+}+_+!+!+-+$+.+.+N.N.H.H.H.O $ ", +" a y h+k+k+m+m+m+m+m+m+m+m+m+m+m+m+m+k+k+h+h+h+h+h+h+h+h+h+h+g+h+g+h+h+g+h+h+b+b+g+h+g+b+b+b+g+g+b+b+b+b+b+b+g+g+b+b+b+g+b+b+b+b+b+b+b+b+b+b+c+c+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+a+a+a+a+a+a+7+3+3+3+}+}+_+!+!+-+.+.+T.N.N.H.F.w.q 3 ", +" Y / 3+e+e+e+e+e+e+e+e+e+e+e+e+e+e+e+e+e+e+e+e+e+e+e+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+6+6+9+9+9+9+6+6+6+6+6+6+6+6+6+9+9+6+6+6+6+6+6+4+e.C + . . + C e.4+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+3+3+^+_+3+!+;+;+;+U.X.X.N.H.H.H.H.0., K ", +" @ d.]+]+]+/+/+:+:+:+:+:+:+:+:+/+/+]+]+]+]+]+=+]+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+^+` . > : 5 9 9 4 ] > . T &+=+=+=+=+=+*+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+*+=+*+#+#+#+ +Y.Y.Y.O.O.O.J.v.v.v.m.d.d.d.4.#.@ ", +" $ ..]+]+]+/+/+:+:+:+:+:+:+:+:+/+/+]+]+]+]+]+=+=+]+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+*+*+*+*+T.# > 0 l l k k k g g g 5 > $ F.*+*+*+*+*+*+=+=+=+=+=+=+*+*+*+*+*+*+=+=+=+=+*+*+=+=+=+=+=+*+=+=+=+*+=+=+*+*+=+=+*+=+*+#+ + + +Y.Y.Y.O.O.O.J.C.v.v.v.m.d.d.4.4.L $ ", +" e u =+]+]+/+/+:+:+:+:+:+:+:+:+:+/+]+]+]+]+]+]+=+=+=+=+=+=+=+=+=+=+=+=+=+=+*+*+=+=+=+=+=+*+*+*+*+*+*+=+=+*+*+#+#+#+#+#+*+*+*+*+*+*+K.+ : l l l l k k k g g g g b ] @ k.*+*+*+*+#+ +#+#+#+#+#+ +#+#+#+*+*+*+*+*+*+*+*+*+#+#+*+*+*+*+=+#+ +*+=+*+*+=+*+#+*+*+#+ +#+ + +Y.Y.Y.O.O.J.C.v.v.v.m.d.d.d.4.4.d | ", +" =.) Y.]+]+]+/+/+:+:+:+:+:+:+:+:+/+]+]+]+]+=+=+=+=+=+=+=+=+=+=+*+*+=+=+#+*+*+*+*+*+*+*+*+#+#+#+#+#+#+#+#+ + +#+#+#+#+#+#+*+*+#+#+#+$+@ 4 l l l l l l k k g g g b b b : @ X.#+#+#+#+#+#+#+#+#+#+ +#+#+ +#+#+#+#+#+#+#+#+#+ +#+*+*+#+ +#+#+ +*+*+#+ +#+*+ +#+#+ +#+#+ +Y. + +Y.Y.O.J.J.C.v.v.v.d.d.d.8.4._., N ", +" @ 4.]+]+]+]+/+:+:+:+:+:+:+:+:+]+]+]+]+]+]+=+=+=+=+=+=+*+*+=+*+#+#+#+#+#+*+*+#+#+#+#+#+ +#+#+#+#+#+#+#+#+#+ + +#+ +#+#+#+#+ +#+#+^+, ] l l l l l l l k k g g g b b 0 0 ' , &+#+#+#+ +#+#+ +#+#+ + +#+#+#+#+#+#+#+#+#+#+ +#+#+#+#+#+#+ +#+ +#+#+#+#+ + +#+ + + +#+#+ +Y.Y. +Y.Y.O.O.J.J.C.v.v.m.d.d.4.8.4.X @ ", +" | L ]+]+]+]+/+:+:+:+:+:+:+:+:+/+]+]+]+]+]+=+=+=+=+=+#+#+*+*+#+ +#+#+#+ +#+#+ +#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+ + + + + + + + + + +*.! l l l l l l l l k k k g g b b 0 0 9 ! *. + + + + + +Y.Y. + + +#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+ + +#+#+#+#+ +#+#+#+ + + +#+#+ +Y. + +Y.Y.O.O.O.J.C.v.v.m.m.d.4.4.4.4.u { ", +" Y [ +]+]+]+]+:+:+:+:+:+:+:+:+/+]+]+]+]+=+=+=+=+=+=+#+#+#+*+ +#+#+#+#+#+#+#+ + + + + + + + + + + + + +#+#+#+#+#+#+#+#+ + + + + + +!+. 0 l l l l l l l l l k k g g b b 0 0 9 < @ U. + + + + + + +#+#+#+#+#+#+ + + + + + + + + + + + + +Y.Y. +#+#+#+#+#+#+ + +#+ + + + + + +Y.Y.Y.Y.W.O.O.J.J.C.v.v.v.m.d.4.4.4._.) F ", +" @ d.]+]+]+]+/+:+:+:+:+:+:+:+/+]+]+]+=+=+=+=+=+*+#+#+ +#+ +#+ + +Y.#+&+&+;+;+.+T.K.K.s.s.7.7.7.7.-.*.*.` T T G G G G G G h h h h h I > l l l m m m l l l l k k g g b b 0 9 9 5 = w h h h h h G G G G G G T T ` *.*.-.7.7.7.7.s.s.x.K.N..+;+&+&+&+#+Y. + +Y. +#+ + + + +Y. +Y. +Y.Y.Y.W.O.J.J.J.v.v.v.m.d.d.8.4.4...@ ", +" , X ]+]+]+]+/+:+:+:+:+:+:+:+/+]+]+]+=+*+)+!+.+K.x.s.7.*.` G h _ $ . . . . . . # , 8 1 s c B B J J M U U +.+.+.+.).).).).).^.5.5.5.b.# 4 l l l m m m m l l l k k g g b b 0 9 5 5 ] # 5.5.5.5.^.).).).).).+.+.+.+.U U M J J B B c s 1 8 , $ . . . . . . $ _ h G ` *.7.s.s.K.X.;+&+Y.Y.Y.Y.W.O.J.J.C.v.v.m.m.d.4.8.4.4.A # ", +" N d =+]+]+]+/+:+:+:+:+:+:+6+!+x.7.*.G [ . . . - 1 c B J U +.^.5.j.z.R.@+(+5+j+u+u+u+u+u+u+u+t+u+u+u+u+u+t+u+u+t+u+u+u+t+t+u+u+u+u+t+2+# k l l l m m m m m l l k k g g b 0 0 9 5 5 4 # 2+t+t+t+t+t+t+t+t+u+t+t+u+u+t+t+u+u+s+u+t+s+s+s+s+s+s+q+f+[+'+@+L.u.j.5.).+.U J s c _ - . . . [ G ` 7.s.H.C.C.v.v.v.m.d.d.4.4.4._.[ w ", +" @ m.]+]+]+]+:+:+/+_+7.T , . $ s J +.^.j.V.[+u+A+A+w+w+w+w+w+t+u+t+w+t+t+w+t+t+t+t+t+t+t+t+t+u+t+t+t+w+t+u+t+t+u+t+w+t+u+u+t+t+t+t+u+i.; l l l l l m m m m l l k k g g b 0 9 5 5 4 4 > [.u+t+t+t+t+u+u+t+t+t+u+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+s+u+t+s+s+u+s+s+s+s+q+q+q+q+f+'+z.5.&.U C c $ . , G -.r.m.m.d.d.4.4.4.#.@ ", +" , X ]+]+]+]+/+:+3+c # J ).z.0+B+C+B+B+A+A+A+A+A+A+A+A+A+A+A+A+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+v : l l l l l m m m m l l k k g b b 0 9 5 5 4 4 ' n w+w+w+t+t+w+w+w+t+t+w+t+t+t+t+u+t+t+u+t+t+u+u+t+t+t+t+t+t+t+u+t+u+u+t+u+s+s+s+s+s+q+q+q+q+o+j+f+f+f+0+0+R.|.U s # [ p.m.d.8.4.4.4.A $ ", +" S } *+]+]+]+]+:+/+1 +.A+E+E+E+E+C+C+C+C+B+B+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+x++ 5 l l l l l m m m m l l k k g b b 0 9 5 5 4 4 ] @ u+A+A+w+w+w+w+A+A+w+w+w+A+w+w+w+w+w+w+w+w+w+w+t+t+t+t+t+t+t+u+u+t+u+u+t+t+t+t+u+s+s+s+q+q+q+j+j+f+f+0+0+0+5+(+(+Z.M _ o.d.d.4.4.4._.~ z ", +" @ d.]+]+]+]+/+:+{+, C+E+E+E+E+E+E+E+C+C+C+B+B+B+B+B+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+x+. b k l l l l l m m l l l k k g b b 0 5 5 4 4 4 : . q+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+w+w+w+w+w+w+w+w+t+t+w+u+t+t+u+s+s+s+s+q+o+j+f+f+0+5+5+[+(+'+'+@+, 6.m.d.4.4.4.4...@ ", +" _ X ]+]+]+]+/+/+:+:.U E+E+E+E+E+E+E+E+E+E+E+C+C+B+B+B+B+B+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+B+B+A+A+B+B+A+A+A+B+B+A+A+A+A+A+A+B+v++ g k k l l l l l l l l l k g g b 0 9 5 5 4 4 2 < . f+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+w+A+w+t+t+t+u+u+s+s+q+o+j+f+f+0+5+5+(+'+'+@+@+B ` m.d.d.4.4.4.4.u - ", +" Y ~ +]+]+]+]+:+:+:+G |.E+E+E+E+E+E+E+E+E+E+E+E+E+C+C+B+B+B+B+B+B+B+B+B+B+B+A+B+B+B+B+B+B+B+B+B+B+B+B+B+B+A+B+B+B+A+A+A+B+B+B+A+A+A+A+A+B+v++ k k k k l l l l l l l l k g g b 0 9 5 4 4 4 < < + 1+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+w+u+t+t+s+s+q+j+f+f+0+5+5+(+'+@+@+@+Z p m.m.d.d.4.4.4._.) K ", +" @ 4.]+]+]+]+/+:+:+/+$ R.E+E+E+E+E+E+E+E+E+E+E+E+E+E+C+C+C+C+C+C+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+n+# k k k k k l l l l l l k k g g b 0 9 5 4 4 2 < < + 1+B+B+B+B+B+B+A+A+B+B+B+B+B+A+A+B+B+A+A+B+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+w+t+t+u+s+q+o+f+f+0+[+[+(+'+@+Z.V.).$ v.m.m.d.4.4.4.4.X @ ", +" | L ]+]+]+]+/+/+:+:+6+. f+C+E+E+E+E+E+E+E+E+E+E+E+E+E+E+E+C+C+C+C+B+B+B+B+B+C+C+B+B+C+B+B+B+B+B+B+B+B+B+B+B+B+C+B+B+B+B+B+B+C+B+B+B+B+B+B+B+v++ k k k k k l l l l l k k k g b 0 0 5 5 4 4 < < < + 1+B+B+B+B+B+B+B+A+B+B+B+B+B+B+A+B+B+B+B+B+B+B+B+A+B+B+B+A+A+B+A+A+A+A+A+A+A+A+A+A+A+w+t+t+s+q+o+j+f+0+5+(+'+@+@+V.P.f.. o.v.m.d.d.8.4.4.4.u { ", +" ~.) +]+]+]+/+/+:+:+:+!+# B+C+C+E+E+E+E+E+E+E+E+E+E+E+E+E+E+E+C+C+C+C+C+C+C+B+C+C+B+B+C+C+B+B+C+C+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+x+. g k k k k k k k k k k k g g b 0 0 5 4 4 2 < < : . j+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+A+B+B+A+A+B+A+A+A+A+A+A+A+A+w+t+u+u+q+q+j+f+0+[+(+'+@+@+P.L.L.@ p.v.m.m.d.d.4.4.4._., S ", +" @ _.]+]+]+]+/+:+:+:+:+{+_ B+C+C+E+E+E+E+E+E+E+E+E+E+E+E+E+E+C+C+C+C+C+B+C+C+B+C+C+B+B+C+C+B+B+B+C+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+x+. 0 g g g k k k k k k k k g b b 0 9 5 4 4 2 < < ] . s+B+B+B+B+B+B+B+B+B+B+B+B+B+B+A+A+B+B+B+B+B+B+A+B+B+B+A+A+B+B+A+B+B+A+B+B+A+A+A+A+A+w+t+t+u+q+q+j+f+5+[+(+'+@+V.R.L.L., 9.v.v.m.d.d.4.4.4.4.L @ ", +" w y h+h+h+h+k+e+m+m+m+m+~+f B+B+C+C+E+E+E+E+E+E+E+E+E+E+E+E+E+C+C+C+C+C+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+A+B+B+B+A+$ 4 g g g g k k k k k g g g b 0 0 9 5 4 4 < < < ] $ A+A+A+A+A+A+A+B+B+B+B+B+B+B+B+A+A+B+B+B+B+B+B+A+B+B+B+A+A+B+B+B+B+B+A+B+A+A+A+A+A+A+A+w+t+u+q+o+j+f+5+[+(+'+@+P.L.L.z.1 7..+X.N.H.H.H.w.p.r.0.d 6 ", +" $ -+h+k+k+m+m+m+m+m+m+m+S.C B+B+C+C+E+E+E+E+E+E+E+E+E+E+E+E+E+C+C+C+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+A+A+A+A+B+B+B+A+A+B+A+A+A+A+A+A+A+A+A+B+B+A+A+n : g g g g g g k k g g g b b 0 9 5 4 4 2 < < < ; n A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+B+A+A+A+A+B+A+A+A+A+A+A+w+w+u+s+q+o+f+f+5+(+'+@+Z.R.L.z.u.8 :..+X.T.N.H.x.x.r.r.k./.$ ", +" - >.h+k+k+k+m+m+m+m+m+m+m+e.J A+B+B+C+E+E+E+E+E+E+E+E+E+E+E+E+E+C+C+B+B+B+B+B+A+B+B+A+A+A+A+B+A+A+A+A+A+A+A+A+A+A+A+A+B+A+A+A+A+A+A+A+A+A+A+A+A+A+A+[.; g g g g g g g g g g g b 0 0 9 5 4 2 < < < < = &.A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+w+u+t+s+q+j+f+0+5+(+@+@+V.L.L.u.u.f *.-+.+X.T.N.H.A.x.r.r.k.H $ ", +" S 7 b+h+k+k+m+m+m+m+m+m+m+m+:.U A+A+B+B+C+E+E+E+E+E+E+E+E+E+E+C+C+B+B+B+B+A+A+B+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+2+& g g g g g g g g g b b b 0 0 5 4 4 2 < < : : % `.A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+w+t+t+t+s+o+j+f+0+5+(+@+@+V.L.z.u.u.s ` -+.+.+X.N.H.H.w.x.r.k.k.[ z ", +" @ H.h+h+k+m+m+m+m+m+m+m+m+m+;.W A+A+A+B+C+C+E+E+E+E+E+E+E+E+C+C+C+B+B+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+x+. 9 b b b g g g g b b b 0 0 9 5 4 4 2 < < : ] . j+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+w+u+t+u+q+q+j+f+0+5+(+@+Z.R.L.u.j.j.C T ;+-+.+.+T.N.H.x.x.r.k.k.>.@ ", +" | O h+k+k+k+m+m+m+m+m+m+m+m+m++.+.A+A+A+B+C+C+E+E+E+E+E+E+E+E+C+B+B+B+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+e ] b b b b b b b b b 0 0 9 5 5 4 4 < < : : ; e A+A+A+A+A+A+A+A+A+A+A+A+A+w+w+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+w+w+t+t+t+s+q+q+j+f+0+5+'+@+Z.R.L.u.j.j.C P !+-+.+.+X.N.N.H.w.r.r.r.k.y ( ", +" }., 3+h+k+k+m+m+m+m+m+m+m+m+m+k+W +.A+A+A+A+B+C+E+E+E+E+E+E+C+C+B+B+B+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+w+w+w+w+w+w+w+w+w+w+w+w+w+w+D.! b b b b b b b b 0 0 9 5 5 4 4 2 < < : : * b.w+w+w+w+w+w+w+w+w+t+t+w+w+w+w+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+w+w+A+A+A+w+u+u+u+u+s+q+o+j+0+5+[+'+@+V.P.z.u.j.f.B P !+;+$+.+.+T.N.H.x.x.r.r.r.6.- Y ", +" $ 1.h+h+k+m+m+m+m+m+m+m+m+m+k+h+W &.w+A+A+A+B+C+C+C+E+E+C+C+C+B+B+B+A+A+A+A+A+A+A+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+A+A+A+A+A+A+A+A+A+A+w+w+w+w+w+w+w+w+w+w+q+. 5 b b b b 0 0 0 0 0 5 5 5 4 2 < < : : ] . [+w+w+w+w+w+w+w+w+w+w+w+A+A+A+A+A+A+A+A+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+t+t+w+A+A+A+w+t+u+t+s+q+q+j+f+0+5+[+'+@+V.R.z.u.j.j.C G !+!+;+-+.+X.T.N.F.A.r.r.r.k.R + ", +" K q h+h+k+m+m+m+m+m+m+m+m+m+m+k+h+P &.t+A+A+A+A+B+C+C+C+C+C+C+C+B+B+A+A+A+A+A+w+w+A+A+A+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+Q ; 0 0 0 0 0 0 0 9 9 5 5 4 4 < < < : : > I w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+t+t+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+A+w+w+w+u+t+t+u+s+s+q+j+f+0+5+[+'+@+V.L.u.u.j.j.C G _+!+!+;+.+.+T.N.N.F.x.x.r.k.k.7 o ", +" @ T.h+k+k+m+m+m+m+m+m+m+m+m+k+k+h+P ).t+w+A+A+A+B+B+C+C+C+C+B+B+B+A+A+A+A+A+A+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+t+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+2+# 5 0 0 0 0 0 9 5 5 5 4 4 2 < < : : : + `.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+t+t+w+u+t+t+s+s+q+o+j+f+0+5+[+'+Z.P.L.u.u.j.j.J G _+!+!+;+-+.+X.T.N.H.x.w.r.r.k.!.@ ", +" 3 V h+h+k+m+m+m+m+m+m+m+m+m+k+k+h+g+J ^.t+w+A+A+A+B+B+B+C+C+C+B+B+B+A+A+A+A+A+A+w+w+t+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+v ' 0 0 0 9 9 5 5 5 4 4 4 2 < < : : > e u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+w+u+t+t+s+s+q+o+f+f+0+5+(+'+@+R.L.u.j.j.b.J p _+_+!+!+-+-+.+X.N.F.H.x.x.r.k.k.E { ", +" (., 3+h+k+k+m+m+m+m+m+m+m+m+m+k+h+g+b+B ^.u+w+w+A+A+B+B+B+B+B+B+B+B+B+A+A+A+A+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+2++ 4 9 9 9 5 5 5 4 4 4 2 < < < : ] @ >+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+q+q+o+f+f+0+[+(+'+@+P.L.u.j.f.b.M h _+_+_+!+;+-+.+.+T.N.F.H.w.r.r.k.6., S ", +" $ 6.h+h+k+m+m+m+m+m+m+m+m+m+k+h+g+b+b+B ^.u+t+w+A+A+A+A+B+B+B+B+B+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+Z = 9 9 5 5 5 5 4 4 4 2 < < < : * U u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+q+q+q+j+f+f+0+5+(+'+Z.R.L.u.j.j.b.J p }+_+_+!+!+;+-+.+X.N.N.F.x.x.r.k.k.V @ ", +" F q h+h+k+k+m+m+m+m+m+m+m+m+k+h+h+g+b+b+J ^.t+u+w+A+A+A+A+B+B+B+B+B+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+s+^ ; 5 5 5 5 5 4 4 2 < < < < > , q+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+q+q+q+j+f+f+0+5+(+'+Z.R.L.z.j.f.b.J p }+}+}+_+!+!+;+$+.+T.N.H.H.w.r.r.k.k.7 o ", +" @ .+h+k+k+k+m+m+m+m+m+m+m+k+k+h+h+b+b+a+J ).t+u+w+A+A+A+A+B+B+B+B+B+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+n+# ' 5 5 5 4 4 4 2 < < < ; # 1+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+q+q+q+j+f+f+0+[+'+'+Z.R.L.u.j.j.b.J p }+}+}+_+!+!+!+-+.+.+T.N.F.x.r.r.r.k.{.@ ", +" 3 V h+h+k+m+m+m+m+m+m+m+m+m+k+k+h+g+b+a+a+J ).t+u+w+A+A+A+A+B+B+B+B+B+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+2+# ; 4 4 4 4 2 2 2 < > # >+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+q+q+q+j+f+f+0+[+'+'+Z.R.L.u.j.j.b.J p }+3+}+_+_+!+!+;+$+.+X.N.N.F.x.x.r.k.k.E { ", +" }., 3+h+k+k+m+m+m+m+m+m+m+m+m+k+h+h+b+b+a+a+P ).t+u+w+A+A+A+A+B+B+B+B+B+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+n+1 % ' < 4 2 : ' # 1 1+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+q+q+q+j+f+f+0+[+(+'+Z.R.L.u.j.j.5.B G 3+3+}+_+_+_+!+!+-+.+.+T.N.F.x.w.r.r.k.6.- ", +" $ <.h+k+k+k+m+m+m+m+m+m+m+m+k+h+h+g+b+a+a+7+P &.t+u+w+w+A+A+A+B+B+B+B+B+A+A+A+A+A+t+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+B.1 . + + . 1 B.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+s+u+q+q+q+j+f+f+5+[+(+'+Z.R.z.u.j.j.5.C G 3+}+}+}+}+_+_+!+-+-+.+X.T.N.H.A.r.r.k.k.R # ", +" N h g+h+k+m+m+m+m+m+m+m+m+m+k+h+h+h+b+a+a+a+3+P &.s+t+t+w+A+A+A+B+B+B+B+A+A+A+A+A+w+t+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+j+1+1+j+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+s+s+q+q+q+j+f+0+5+[+(+'+Z.R.z.u.j.b.5.C G 3+}+3+3+}+_+_+!+!+;+.+.+X.N.F.x.x.r.r.k.k.[ w ", +" @ H.h+h+k+m+m+m+m+m+m+m+m+k+k+h+h+b+a+a+a+a+7+P &.u+t+t+A+A+A+A+B+B+B+B+A+A+A+A+A+w+w+A+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+s+s+s+q+o+j+f+0+0+[+(+@+V.L.z.u.j.j.b.B G 3+3+}+}+}+}+_+!+!+!+$+.+.+T.N.F.A.x.r.r.k.$.@ ", +" e O e+e+e+e+e+e+e+e+e+e+e+e+e+e+e+6+6+6+6+6+^+^+W +.t+t+t+w+A+A+A+A+B+B+B+A+A+A+A+A+A+A+w+t+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+u+s+s+s+q+q+o+f+f+0+[+(+@+V.L.L.u.j.j.5.B T ^+^+^+^+^+!+!+!+!+;+;+U.U.U.H.H.H.p.p.p.0.0.t 3 ", +" , O.]+]+]+]+/+:+:+:+:+:+/+]+]+]+=+=+=+=+*+ +#+ +` W t+t+t+w+A+A+A+A+A+B+B+A+A+A+A+A+A+A+w+t+t+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+u+s+s+s+q+q+j+f+f+0+[+'+@+Z.R.L.u.j.b.5.s O + +Y. + +Y.W.W.O.O.J.J.C.v.v.m.m.d.8.4.4.4.'.$ ", +" # ..]+]+]+]+/+/+:+:+:+:+/+]+]+]+=+=+=+=+#+ +#+ +Y.*.U u+t+u+w+A+A+A+B+B+B+B+A+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+q+j+f+f+0+[+'+@+Z.R.z.u.j.b.5.s R Y.Y.Y. +Y.Y.Y.Y.Y.O.J.J.J.C.v.v.m.d.d.8.4.4._.A $ ", +" @.} *+]+]+]+]+/+:+:+:+:+:+/+]+]+]+=+=+=+*+ +#+ +Y.Y.-.M u+t+t+w+A+A+A+B+B+B+B+B+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+q+j+f+f+0+[+(+'+Z.L.z.u.j.b.5.s V Y.Y. +Y.Y.Y.Y.Y.Y.O.O.J.J.J.v.v.m.m.d.4.4.4.4._.) K ", +" @ 4.]+]+]+]+/+:+:+:+:+:+/+]+]+]+]+=+=+=+#+ +#+ +Y. +:.J s+t+t+w+A+A+A+A+B+B+B+B+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+q+j+f+f+5+[+(+@+V.L.z.u.j.b.5.n . +Y. + + +Y.Y.Y.Y.O.O.O.J.J.C.v.v.m.m.d.4.4.4._.X @ ", +" I i ]+]+]+]+]+/+:+:+:+:+/+]+]+]+]+=+=+=+*+#+#+ +Y.Y. +7.J u+t+u+w+A+A+A+A+B+B+B+B+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+q+j+f+f+5+[+(+@+V.L.z.u.j.b.5.c $. +Y.Y. +Y. +Y.Y.Y.W.O.O.J.J.J.v.v.v.m.d.4.4.4._._.d e ", +" @ v.]+]+]+]+/+/+:+:+:+/+/+]+]+]+=+=+=+=+#+#+#+ +Y. + +7.B u+t+u+w+A+A+A+B+B+B+B+B+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+q+j+f+f+5+(+'+@+Z.L.z.u.f.b.b.c >.Y.Y.Y. + +Y.Y.Y.Y.W.W.O.J.J.J.C.v.v.m.d.d.8.4.4._.#.@ ", +" e L ]+]+]+]+]+/+:+:+:+:+/+]+]+]+=+=+=+=+#+ +#+ +Y. + +Y.s.s u+t+t+w+A+A+A+A+B+B+A+A+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+q+j+f+0+[+(+'+@+V.L.z.u.j.b.5.8 /.Y. + +Y.Y.Y. +Y.Y.Y.W.O.O.O.J.C.C.v.v.m.d.d.4.4.4._.u _ ", +" , O.]+]+]+]+/+/+:+:+:+:+/+]+]+]+=+=+=+*+ +#+ +Y.Y. + +Y.s.s t+t+u+w+A+A+A+A+B+B+A+A+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+j+V.5.^.&.+.+.).5.R.f+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+f+R.5.).+.+.&.^.5.V.j+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+q+q+q+j+f+0+5+(+'+@+V.L.z.u.f.j.5.1 <.Y. + +Y.Y. +Y.Y.Y.Y.Y.Y.O.O.J.J.C.v.v.m.d.d.4.4.4._.'.$ ", +" - ..]+]+]+]+/+/+:+:+:+:+:+/+]+]+]+=+=+=+#+ +#+ +Y. + +Y.Y.s.c s+t+u+t+A+A+A+A+B+B+B+B+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+j+u.U | . . . . . . . . . . . _ M j.f+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+f+j.M _ . . . . . . . . . . . | U u.o+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+q+q+q+j+f+0+0+[+'+@+Z.L.z.u.j.b.5./ 2.Y.Y. + +Y. + + +Y.Y.Y.Y.O.O.J.J.J.C.v.v.m.d.d.4.4.4._.A $ ", +" =.[ *+]+]+]+]+/+:+:+:+:+:+/+]+]+]+=+=+=+=+ +#+ + + + +Y.Y.Y.K.1 s+t+u+t+w+A+A+A+B+B+A+A+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+@+U $ . . . . . . . . . . . . . . . . . @ C u.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+V.J @ . . . . . . . . . . . . . . . . . $ J L.u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+q+j+f+0+[+(+'+@+V.L.z.j.j.b.5., 0.Y.Y.Y. + +Y.Y.Y. +Y.Y.Y.W.O.O.J.J.C.v.v.v.d.d.4.4.4._._.) N ", +" @ _.]+]+]+]+]+/+:+:+:+:+/+]+]+]+=+=+=+=+#+#+ +Y. + +Y.Y.Y.Y.N., s+t+u+w+A+A+A+A+A+B+B+B+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u.f . . . . @ s +.j.'+f+q+u+s+f+'+j.&.C $ . . . . 1 f.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+b.1 . . . . $ C &.u.(+f+s+u+q+f+'+j.+.s @ . . . . f z.u+u+u+u+u+u+u+t+t+t+s+s+q+q+j+f+0+5+(+'+@+V.L.z.j.j.b.5.- p.Y.Y.Y.Y. + +Y. +Y.Y.Y.Y.W.W.O.J.J.J.C.v.v.m.d.4.4.4.4._.L @ ", +" N i =+]+]+]+]+/+:+:+:+:+/+]+]+]+=+=+=+=+#+ +#+ +Y. + +Y.Y.Y.Y..+@ s+u+t+t+A+A+A+A+A+B+B+B+A+A+A+A+A+A+w+w+w+u+u+u+u+u+V.s . . . + B u.j+u+u+u+u+u+u+u+u+u+u+u+u+u+q+z.J @ . . . 8 z.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+z.8 . . . $ M z.q+u+u+u+u+u+u+u+u+u+u+u+u+u+j+j.C @ . . . s V.u+u+u+u+u+t+t+t+s+s+q+o+f+0+0+5+(+'+@+R.L.z.j.j.b.5.+ l.Y.Y.Y.Y. + +Y. + + +Y.Y.Y.W.O.O.J.J.J.v.v.v.m.d.8.4.4.4._.} v ", +" @ m.]+]+]+]+]+/+:+:+:+:+/+]+]+]+=+=+=+*+ +#+ + + + +Y.Y.Y.Y.Y.;+. j+u+t+t+w+A+A+A+B+B+B+B+A+A+A+A+A+A+w+w+w+u+u+u+j+U @ . . $ +.[+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+0+).- . . . J f+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+0+J . . . - ).f+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+[++.$ . . @ U j+u+u+u+t+t+t+s+q+q+o+f+f+0+5+(+'+@+R.L.u.j.j.b.5.. E.Y.Y.Y.Y.Y.Y.Y.Y. +Y.Y.Y.Y.Y.Y.O.J.J.J.C.v.v.m.d.d.4.4.4._...@ ", +" e A =+]+]+]+]+/+:+:+:+:+/+]+]+]+]+=+=+=+#+ +#+ + + +Y.Y.Y.Y.Y.Y.&+. 5+s+t+t+w+A+A+A+A+A+A+B+A+A+A+A+A+A+w+w+w+u+u+L._ . . @ Z 0+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+j++.$ . . { j.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+j.- . . $ &.j+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+0+W @ . . _ L.u+u+t+t+t+s+q+q+o+f+f+0+5+(+'+@+P.L.u.j.j.b.^.. E.Y.Y.Y.Y.Y.Y. + + + + +Y.Y.Y.Y.O.O.J.J.C.v.v.m.m.d.8.4.4._._.i | ", +" $ J.]+]+]+]+]+/+:+:+:+/+]+]+]+]+=+=+=+*+ +#+ + + + +Y.Y.Y.Y.Y.Y.&+. '+u+u+t+w+A+A+A+A+A+A+A+A+A+A+A+A+A+w+w+w+u+).@ . . n (+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+0+C . . . U s+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+s++.. . . C [+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+'+n . . @ +.u+t+t+t+s+q+q+o+j+f+0+[+(+'+Z.P.L.u.j.j.b.^.. C.Y.Y.Y.Y.Y.Y. + +Y. +Y.Y.Y.Y.Y.O.O.O.J.J.C.v.v.m.d.d.4.4.4._.#.@ ", +" | X ]+]+]+]+]+/+:+:+:+:+/+]+]+]+]+=+=+*+#+ +#+ + + +Y.Y.Y.Y.Y.Y.Y.Y.. Z.t+t+u+w+A+A+A+B+B+B+B+A+A+A+A+A+A+w+w+o+C . . $ ^.u+u+u+u+u+u+u+u+s+V.5.U s f | - _ 8 s U ^.L.q+u+u+u+u+u+u+u+u+5.- . . s 0+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+0+s . . - b.u+u+u+u+u+u+u+u+q+L.^.U s 8 _ - | f s U 5.V.s+u+u+u+u+u+u+u+u+^.$ . . B j+t+t+s+q+q+q+j+f+0+5+(+'+Z.P.L.u.j.b.5.&.. O.Y.Y.Y.Y.Y.Y.Y. + + + + +Y.Y.Y.W.O.O.J.J.J.C.v.m.m.d.8.4.4._._.u { ", +" , Y.]+]+]+]+]+:+:+:+:+:+/+]+]+]+=+=+=+#+ + + + + + +Y.Y.Y.Y.Y.Y.Y.Y.@ z.s+t+u+w+A+A+A+A+B+B+B+B+A+A+A+A+A+w+f+f . . _ @+u+u+u+u+u+u+o+5.J $ . . . . . . . . . . . . . $ C 5.f+u+u+u+u+u+u+(+8 . . _ (+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+[+| . . 1 (+u+u+u+u+u+u+f+|.C $ . . . . . . . . . . . . . - J b.o+u+u+u+u+u+u+@+_ . . 8 5+t+s+q+q+q+j+f+0+5+(+'+@+P.L.u.j.b.5.&.. W.Y.Y.Y.Y.Y.Y.Y. + +Y.Y. + +Y.Y.Y.O.O.O.J.J.J.v.v.m.m.d.4.4.4._.'., ", +" , #.]+]+]+]+]+/+:+:+:+:+/+]+]+]+=+=+=+=+#+ +#+Y. + +Y.Y.Y.Y.Y.Y.Y.Y.Y., j.u+t+u+w+A+A+A+A+A+B+B+A+A+A+A+A+A+5+1 . . s f+u+u+u+u+u+'+U $ . . . . . $ f C J U J B f $ . . . . . $ M @+u+u+u+u+u+o+J . . , @+u+u+u+u+u+u+u+u+u+u+u+u+u+u+@+{ . . J o+u+u+u+u+u+Z.M $ . . . . . $ f B J U J C f @ . . . . . $ W '+u+u+u+u+u+f+s . . 1 5+s+q+q+q+j+0+0+5+(+@+Z.P.L.u.j.j.b.+.. Y.Y.Y.Y.Y.Y.Y.Y.Y. + +Y. + +Y.Y.Y.W.O.O.O.J.J.v.v.v.m.d.8.4.4._._.L $ ", +" =.~ *+]+]+]+]+]+:+:+:+:+/+]+]+]+]+=+=+=+*+ +#+ +Y. + +Y.Y.Y.Y.Y.Y.Y.Y.Y.c 5.u+t+u+w+A+A+A+B+B+B+A+A+A+A+A+A+u+| . . M s+u+u+u+u+(+J @ . . . $ J 5.(+s+u+u+u+u+u+u+u+u+[+f.M - . . . . C @+u+u+u+u+u++.. . - f+u+u+u+u+u+u+u+u+u+u+u+u+f+- . . +.u+u+u+u+u+@+B . . . . - M f.[+u+u+u+u+u+u+u+u+s+(+5.B $ . . . @ J (+u+u+u+u+s+J . . | j+q+q+o+f+0+0+[+(+@+Z.R.z.u.j.b.5.U $ Y.Y.Y.Y.Y.Y.Y.Y.Y. + +Y. + + +Y.Y.Y.W.O.O.J.J.C.v.v.v.d.4.8.4.4._._., N ", +" $ '.]+]+]+]+/+/+:+:+:+:+/+]+]+]+*+=+*+*+#+#+#+Y.Y.#+Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.p ^.s+s+t+w+A+A+A+A+B+B+B+A+A+A+A+A+A+L.$ U u+u+u+u+j+U @ . . . s j.s+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+z.C @ . . @ M 0+u+u+u+u+).@ |.u+u+u+u+u+u+u+u+u+u+u+u+u+u+5.@ +.u+u+u+u+0+J @ . . @ B z.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+s+j.s . . . @ W j+u+u+u+u+W @ u.q+q+q+j+f+f+0+[+(+@+Z.R.z.u.j.b.5.U / Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.W.W.O.O.J.J.C.v.v.m.d.d.4.4._._.L @ ", +" Y h b+h+h+k+m+m+m+m+m+m+m+m+k+h+g+b+b+a+a+a+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+P &.s+t+u+t+A+A+A+A+A+B+B+B+A+A+A+A+A+t+(+w+u+u+u+z._ . . @ B '+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+[+M + . . - b.u+u+u+u+'+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+'+u+u+u+u+b.- . . + M [+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+'+C . . . _ z.u+u+u+t+'+u+q+q+q+j+f+f+0+5+'+@+Z.R.z.u.j.b.b.J h 3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+}+_+_+_+!+!+-+.+.+T.N.F.F.x.r.k.k.9.[ F ", +" @ r.h+h+k+m+m+m+m+m+m+m+m+m+k+h+h+b+b+a+a+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+W +.s+t+u+w+w+A+A+A+B+B+B+B+A+A+A+A+w+t+w+w+u+q+M . . . s Z.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+'+C . . . C f+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+f+B . . . C '+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+Z.n . . . U q+u+t+s+s+q+q+q+j+f+f+0+[+'+@+@+R.z.u.f.b.5.C G 3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+}+}+}+}+_+_+!+;+$+.+X.N.H.H.w.r.k.k.k. .@ ", +" K y h+h+h+k+m+m+m+m+m+m+m+m+k+h+h+g+b+a+a+a+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+;.U s+s+t+w+A+A+A+A+A+B+A+A+A+A+A+A+w+w+A+w+[+f . . $ ).u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+j.- . . _ @+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+'+| . . - 5.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+5.$ . . f [+t+s+s+s+q+o+j+f+0+5+(+'+@+V.L.z.u.f.b.5.s T 3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+}+}+}+_+!+!+-+.+.+X.N.H.x.x.r.k.k.9.7 r ", +" + H.h+h+k+k+m+m+m+m+m+m+m+k+h+h+g+b+a+a+a+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+:.J s+u+t+t+w+A+A+A+A+A+A+A+A+A+A+A+A+A+A+R.- . . n [+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+0+B . . $ j.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+j.$ . . C 0+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+[+f . . - L.s+s+s+q+o+j+f+0+5+(+'+@+V.L.z.u.j.b.5.n ` 3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+}+}+}+}+_+!+!+;+$+.+.+N.N.H.w.r.r.k.k.>.+ ", +" z E h+h+k+k+m+m+m+m+m+m+m+m+k+h+h+b+a+a+a+a+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+e.B s+t+u+w+A+A+A+A+B+B+B+B+A+A+A+A+A+A+z.$ . . M q+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+).@ . @ |.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+5.@ . @ +.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+q+U . . @ j.s+s+q+q+j+f+0+5+(+'+@+V.L.z.u.f.b.5.f :.3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+}+}+}+!+!+!+-+$+.+T.N.F.A.x.r.k.k.k.d a ", +" @ .+h+h+k+m+m+m+m+m+m+m+m+k+h+h+h+b+a+a+a+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+G.s s+t+t+t+A+A+A+A+A+B+j+f+f+0+0+0+0+5.@ . @ 5.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u.$ . @ ^.u+u+u+u+u+u+u+u+u+u+u+u+u+u+^.@ . $ u.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+5.@ . @ |.(+(+(+'+'+@+@+(+'+@+V.L.z.j.j.b.5.8 :.3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+}+}+3+}+_+!+!+-+-+.+X.T.H.F.w.x.r.k.k.{.@ ", +" o O h+h+h+k+m+m+m+m+m+m+m+m+k+h+h+b+b+a+a+a+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+S.f s+s+t+t+w+A+A+A+A+A+_ . . . . . . @ . @ u.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+V.$ . . ).u+u+u+u+u+u+u+u+u+u+u+u+).. . $ V.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u.@ . @ . . . . . . _ (+'+@+V.L.u.j.f.b.5.1 7.3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+_+_+!+!+-+.+.+X.N.F.F.w.r.k.k.k.t 3 ", +" $ $+h+h+k+k+m+m+m+m+m+m+m+k+k+h+g+b+a+a+7+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+S.| s+u+t+t+w+A+A+A+A+A+_ . . . . . . . . . f L.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+Z.s . . . _ (+u+u+u+u+u+u+u+u+u+u+(+_ . . . s Z.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+z.f . . . . . . . . . _ (+'+@+R.L.u.u.j.5.5., s.3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+}+}+}+_+!+!+;+-+.+.+N.N.H.A.r.k.k.k./.@ ", +" | V h+h+k+k+k+m+m+m+m+m+m+m+k+h+g+b+b+a+a+7+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+~+- s+s+t+t+w+A+A+A+A+B+j.5.5.5.5.5.|.Z , . . . s Z.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+'+C . . . - ).q+u+u+u+u+u+u+u+u+u+u+u+u+q+).- . . . C '+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+Z.s . . . , U ^.^.^.^.^.).^.(+'+@+P.L.u.j.b.5.5.$ s.3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+}+_+_+!+!+;+$+.+T.N.F.F.r.r.k.k.k.E ( ", +" $ !+h+h+k+k+m+m+m+m+m+m+m+k+h+h+g+b+a+a+a+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+~+. o+s+s+t+w+A+A+A+A+A+A+A+A+A+A+A+A+A+q+).$ . . . B R.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+@+M @ . . $ +.f+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+f+W $ . . + M @+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+P.B . . . # &.j+s+q+q+j+f+f+0+5+(+'+@+P.L.u.j.b.5.5.. ~+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+}+}+_+!+!+!+-+.+X.N.N.H.w.x.r.k.k.<.$ ", +" ^ .h+h+h+k+m+m+m+m+m+m+m+k+h+h+g+b+b+a+a+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+4+. 5+s+u+t+t+A+A+A+A+A+A+A+A+A+A+A+A+A+w+w+f+W @ . . . f f.q+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+s+u.s . . . @ M [+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+[+M @ . . . s u.s+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+q+b.f . . . @ Z 0+t+t+s+q+q+o+j+f+0+[+(+'+@+P.L.u.j.j.5.).. {+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+}+3+}+_+_+!+!+;+.+.+T.N.F.x.x.r.k.k.9.H { ", +" , 3+h+h+k+k+m+m+m+m+m+m+m+k+k+h+g+b+a+a+a+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+c+. @+u+t+t+t+w+A+A+B+B+B+B+A+A+A+A+A+A+w+w+w+u+[+M @ . . . - U R.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+@++.{ . . . @ B '+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+@+B @ . . . , +.@+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+R.U - . . . @ M [+u+t+t+t+s+q+q+q+j+f+0+5+(+'+@+R.L.u.f.j.5.+.. }+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+}+_+_+_+!+!+$+.+X.T.N.F.A.r.r.k.k.1.$ ", +" _ .h+h+h+k+m+m+m+m+m+m+m+m+k+h+g+b+b+a+a+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+$ z.u+t+u+w+A+A+A+A+A+B+B+A+A+A+A+A+A+w+w+w+u+u+u+5++.- . . . . - M 5.(+u+u+u+u+u+u+u+u+u+u+u+[+b.U _ . . . . $ U (+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+(+U $ . . . . _ U f.[+u+u+u+u+u+u+u+u+u+u+u+'+5.M - . . . . - +.[+u+u+u+t+t+t+s+q+q+o+f+0+0+[+'+'+@+R.L.u.f.j.5.W $ }+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+}+}+}+_+_+!+!+-+$+.+X.N.H.x.w.r.k.k.k.H - ", +" - 3+h+h+k+k+m+m+m+m+m+m+m+k+h+g+b+b+a+a+a+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+| b.u+t+u+t+A+A+A+A+B+B+A+A+A+A+A+A+A+w+w+w+u+u+u+u+u+q+b.s @ . . . . . $ f J U +.).+.W J f - . . . . . . f 5.j+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+f+5.f . . . . . . - f J W +.).+.U J f $ . . . . . @ s b.q+u+u+u+u+u+t+t+t+q+q+q+j+f+0+5+[+(+@+Z.R.z.u.j.b.b.M / 3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+}+_+_+_+!+!+-+.+.+T.F.H.x.r.k.k.k.1.$ ", +" _ $.h+h+h+k+k+m+m+m+m+m+m+k+k+h+g+b+b+a+a+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+B ^.s+u+t+u+w+A+A+A+A+A+A+A+A+A+A+A+A+t+w+w+u+u+u+u+u+u+u+u+[+|.B $ . . . . . . . . . . . . . . . @ s ^.(+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+(+^.s @ . . . . . . . . . . . . . . . $ C 5.[+u+u+u+u+u+u+u+u+t+s+s+q+q+q+j+f+f+5+[+'+@+Z.R.z.u.j.b.5.B p 3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+}+}+}+_+_+!+!+-+.+.+X.N.H.F.x.r.k.k.9.H - ", +" - }+h+h+h+m+m+m+m+m+m+m+m+k+h+g+b+b+a+a+a+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+W +.s+s+t+t+w+A+A+A+A+B+B+A+A+A+A+A+w+w+w+w+u+u+u+u+u+u+u+u+u+u+u+q+Z.^.U s _ $ . . . $ , s M ^.P.q+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+o+R.).M s , $ . . . $ _ s U ^.Z.s+u+u+u+u+u+u+u+u+u+u+u+t+s+s+q+q+q+j+f+0+5+[+'+@+V.L.z.u.j.b.5.B G 3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+}+_+_+_+!+!+-+.+.+N.N.H.w.r.k.k.k.1.$ ", +" _ .h+h+h+k+m+m+m+m+m+m+m+k+k+h+g+b+b+a+a+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+;.M s+s+t+w+A+A+A+A+A+B+A+A+A+A+A+A+A+A+w+t+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+f+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+f+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+u+s+s+s+q+o+j+f+0+5+[+'+@+V.L.z.u.f.b.5.s ` 3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+}+}+}+_+_+!+!+-+$+.+T.N.H.F.x.r.k.k.9.H - ", +" - 3+h+h+h+k+m+m+m+m+m+m+m+k+h+g+b+b+a+a+a+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+e.J s+s+t+u+w+A+A+A+A+A+A+A+A+A+A+A+A+A+w+t+t+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+u+s+s+s+q+q+j+f+0+5+(+'+@+V.L.z.j.j.b.5.c :.3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+}+_+_+_+!+!+-+.+.+N.N.H.w.r.k.k.k.1.$ ", +" ^ .h+h+h+k+k+m+m+m+m+m+m+k+k+h+g+b+b+a+a+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+G.s s+s+t+t+t+A+A+A+A+A+A+B+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+q+j+f+0+[+(+'+@+R.L.u.j.j.b.5.1 7.3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+}+}+}+_+_+!+!+-+$+.+T.N.H.x.r.r.k.k.9.H - ", +" - 3+h+h+h+k+m+m+m+m+m+m+m+k+h+g+b+b+a+a+a+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+S.8 s+s+t+t+t+A+A+A+A+A+A+A+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+o+j+f+0+5+(+'+@+P.L.u.j.j.b.5._ s.3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+}+_+_+_+!+!+-+.+.+N.N.F.x.r.k.k.9.1.$ ", +" | ./+/+/+/+/+/+/+/+/+/+/+/+/+/+/+]+)+*+)+)+)+%+%+)+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+K.- s+s+t+t+w+A+A+A+A+B+B+A+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+j+f+f+0+[+(+'+@+P.L.u.j.j.b.5.$ w.%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+&+&+&+&+O.O.C.C.E.o.o.l.l.4._.4.4.x { ", +" $ O.=+]+]+]+]+/+:+:+/+/+]+]+]+=+=+=+=+#+ +#+ +Y. + +Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.;+. j+s+t+t+t+A+A+A+A+A+A+A+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+q+o+j+j+f+0+5+(+@+@+R.L.u.j.j.b.^.. E.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. + +Y. + + +Y.Y.Y.W.O.O.J.J.C.v.v.m.d.d.4._._._.'.@ ", +" e L =+=+]+]+]+/+/+:+:+/+]+]+]+]+=+=+=+*+ +#+ +Y. + +Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.&+. (+s+t+t+t+w+A+A+A+A+A+A+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+q+q+q+j+f+0+5+(+'+@+P.z.u.j.j.5.).. C.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. + +Y. +Y.Y.Y.Y.W.W.O.J.J.C.v.v.m.d.d.4.4.4._._.u | ", +" @ J.=+]+]+]+]+/+/+:+:+/+]+]+]+=+=+=+=+#+ +#+ +Y. + +Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.. Z.s+t+t+t+A+A+A+A+A+A+B+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+q+q+o+f+0+0+5+(+@+Z.P.z.u.j.b.5.+.. O.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. + +Y. + + +Y.Y.Y.W.O.O.J.J.C.v.v.m.d.4.4.4._._.#.@ ", +" v A =+=+]+]+]+]+/+:+:+/+/+]+]+]+=+=+=+*+ +#+ +Y. + +Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.$ u.s+u+t+u+w+A+A+A+A+A+A+A+A+A+A+A+w+t+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+s+s+q+q+q+j+f+0+5+[+(+@+Z.R.z.u.j.b.5.W $ Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. + +Y. +Y.Y.Y.Y.W.W.O.J.J.J.v.v.m.d.d.4.4._._._.i e ", +" @ v.=+=+]+]+]+]+/+/+/+]+]+]+]+=+=+=+=+#+ +#+ +Y. + +Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.1 5.s+s+t+u+w+A+A+A+A+A+A+A+A+A+A+A+w+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+s+s+q+s+q+j+f+f+5+[+(+@+V.L.z.u.f.b.5.U / Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. + +Y. + + +Y.Y.Y.W.O.J.J.J.v.v.m.d.d.8.4._._._.X @ ", +" Q u =+=+=+]+]+]+]+/+/+/+]+]+]+]+=+=+=+*+ +#+ +Y. + +Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.p ^.s+s+t+t+A+A+A+A+A+B+B+A+A+A+A+A+A+A+w+t+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+u+s+s+s+s+o+j+f+0+5+[+'+@+V.L.z.u.j.b.5.J p Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. + +Y. +Y.Y.Y.Y.W.O.O.J.J.C.v.v.m.d.8.4._._._._.d r ", +" @ 4.=+=+]+]+]+]+]+/+:+/+]+]+]+=+=+=+=+#+ +#+ +Y. + +Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.T +.s+s+t+u+A+A+A+A+A+A+A+A+A+A+A+A+A+A+w+t+t+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+u+s+s+s+q+o+j+f+0+[+(+'+@+R.L.L.u.j.b.5.C E Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. + +Y. + + +Y.Y.Y.W.O.J.J.J.v.v.v.d.4.4.4._._._.X @ ", +" ~.} =+=+=+]+]+]+]+]+/+/+]+]+]+]+=+=+=+*+ +#+ +Y. + +Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.*.M s+s+t+t+w+A+A+A+A+A+A+A+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+q+j+f+0+[+(+'+@+P.R.L.j.j.b.5.s V Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. + +Y. +Y.Y.Y.Y.Y.O.J.J.J.C.v.v.m.d.8.4._._._._.) N ", +" # #.=+=+=+]+]+]+]+]+]+]+]+]+]+=+=+=+=+*+ +#+ +Y. + +Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.7.C s+s+u+t+w+A+A+A+A+A+A+A+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+o+j+f+0+5+(+'+@+P.L.z.j.j.5.5.8 >.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. + +Y. + + +Y.Y.Y.O.O.O.J.C.v.v.m.d.d.4._._._._.A $ ", +" , Y.=+=+=+]+]+]+/+]+]+]+]+]+]+=+=+=+=+#+#+ +Y. + +Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.k.8 q+s+s+t+t+A+A+A+A+A+A+A+A+A+A+A+A+w+t+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+s+u+s+q+q+j+f+f+0+5+(+'+@+R.L.u.j.j.5.5._ 9.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. + +Y. +Y.Y.Y.Y.O.O.O.J.J.v.v.m.d.d.4.4._._._.'.$ ", +" e L =+=+=+=+]+]+]+]+/+]+]+]+]+=+=+=+=+*+ +#+ +Y. + +Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.N.- s+s+s+t+t+A+A+A+A+A+A+A+A+A+A+A+A+w+t+w+t+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+u+s+u+s+q+q+o+j+f+0+5+(+'+@+P.L.u.j.j.5.5.$ p.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. + +Y. + + +Y.Y.O.O.O.J.J.v.v.v.d.d.4.4._._._.'.u | ", +" @ v.=+=+=+=+]+]+]+]+]+]+]+]+]+=+=+=+*+ +#+ +Y. + +Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.;+. f+s+s+u+u+A+A+A+A+A+A+A+A+A+A+A+A+w+w+w+t+t+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+u+s+t+s+q+q+o+j+f+0+5+(+'+Z.R.L.u.j.j.5.^.. E.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. + +Y. +Y.Y.Y.W.O.O.J.J.C.v.v.d.d.4.4._._._.'.... ", +" =.i *+=+=+=+=+]+]+]+]+]+]+]+]+=+=+=+=+*+ +#+ +Y. + +Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.&+. '+q+s+t+u+w+A+A+A+A+A+B+A+A+A+A+A+w+w+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+q+j+f+0+5+5+(+@+V.R.L.u.f.b.5.&.. O.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. + +Y.Y.Y.Y.Y.Y.W.O.J.J.C.v.v.m.d.4.4._._._.'.'.[ K ", +" _ ..*+=+=+=+=+]+]+]+]+]+]+]+]+=+=+=+=+*+ +#+ + + +Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.@ z.s+s+t+t+w+A+A+A+A+A+A+A+A+A+A+A+w+w+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+q+j+f+0+5+[+'+@+V.P.z.u.j.b.5.W @ Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. + + + + +Y.Y.O.O.O.J.C.v.v.m.d.4.4._._._.'.'.A - ", +" @ J.*+*+=+=+=+=+]+]+]+]+]+]+=+=+=+=+*+ +#+ + + + +Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.1 5.s+s+t+t+t+A+A+A+A+A+A+A+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+o+j+f+0+0+[+'+@+Z.R.z.u.j.b.5.M [ Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. + +Y. +Y.Y.Y.W.O.O.J.J.C.v.m.d.4.4._._.'.'.'.#.@ ", +" @.i + +#+*+=+=+=+=+]+]+]+]+=+=+=+=+=+ +#+#+ + + +Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.G &.q+s+u+t+t+A+A+A+A+A+A+A+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+q+o+f+0+5+(+'+@+V.L.u.j.f.5.5.C q Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. + +Y.Y.Y.Y.W.O.J.J.J.v.v.m.d.4.4._._.'.'.'.'.} F ", +" , #. +#+ +#+=+=+=+=+]+]+]+=+=+=+=+=+*+#+#+ + + + +Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.*.U q+s+s+t+t+A+A+A+A+A+A+A+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+q+j+f+0+[+(+'+@+V.L.u.j.j.5.5.s O Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. +Y.Y.Y.O.O.J.J.J.v.v.m.d.8.4._._.'.'.'.#.A # ", +" . v.Y. +#+ +=+=+=+=+=+=+=+=+=+=+=+=+*+*+#+#+Y. + +Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.7.B s+s+u+t+u+w+A+A+A+A+A+A+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+o+j+f+0+5+(+'+@+V.L.u.j.b.5.5.8 >.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. +Y.Y.O.O.O.J.C.v.v.m.d.8.4._._.'.'.'.#.X . ", +" , Y.Y.Y. + +#+=+=+=+=+=+=+=+=+=+=+=+*+#+#+ +Y. + +Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.s.c q+s+s+u+t+w+A+A+A+A+A+A+A+A+A+A+A+w+t+t+t+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+u+u+u+s+q+q+j+f+f+5+[+(+'+@+P.z.u.j.b.5.5._ 9.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. + +Y.Y.Y.O.O.O.O.J.J.C.v.m.d.8.4._._.'.'.'.#.#.$ ", +" } %+%+%+%+%+*+*+*+*+*+]+]+]+]+*+*+*+*+*+*+#+#+*+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+K.$ q+q+s+t+u+w+A+A+A+A+A+A+A+A+A+A+A+w+t+t+t+t+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+u+u+u+s+q+q+o+j+f+5+5+(+@+Z.R.z.u.j.b.5.5.$ H.#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+Y.W.W.W.O.O.O.C.C.E.o.l.4.4._._.'.].].#.#.) ", +" / }+}+3+3+3+3+a+a+b+b+b+b+b+b+b+a+a+a+a+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+4+. [+s+s+t+u+t+A+A+A+A+A+A+A+A+A+A+A+w+w+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+t+s+q+q+o+f+0+5+[+(+@+Z.R.z.u.f.b.5.^.. {+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+}+_+!+!+!+-+.+.+N.H.x.r.k.9.6.<.<./.!.!., ", +" @ -+}+}+3+3+3+3+a+a+b+b+b+b+b+a+a+a+a+a+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+c+. V.q+s+u+t+t+A+A+A+A+A+A+A+A+A+A+A+w+w+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+q+j+f+0+5+(+'+@+Z.R.z.u.j.b.5.+.. }+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+}+_+_+!+!+!+-+.+.+N.H.x.r.k.9.1.<./.{.!.>.@ ", +" @ 6._+_+_+}+3+3+3+a+a+a+a+a+a+a+a+a+a+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+, b.q+q+s+t+t+w+A+A+A+A+A+A+A+A+A+A+w+w+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+q+j+f+0+5+(+'+@+V.R.z.j.j.b.5.U , 3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+_+_+_+!+!+!+$+.+T.N.H.x.k.9.6.1./.{.!.!.O @ ", +" z H _+_+_+_+}+3+3+3+a+a+a+a+a+a+a+a+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+C ).q+s+s+t+t+w+w+A+A+A+A+A+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+o+j+f+0+5+(+'+@+V.L.u.j.j.b.5.C p 3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+_+_+_+!+!+-+.+X.N.H.F.r.k.9.6.<./.!.>.>.d 6 ", +" - ;+!+_+_+}+3+3+3+3+a+a+a+a+a+7+7+a+a+7+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+;.U q+q+s+u+t+w+A+A+A+A+A+A+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+q+j+f+0+5+(+'+Z.V.L.u.j.j.5.5.s T 3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+}+}+_+!+!+-+.+X.N.F.A.r.k.9.6././.!.>.>.$ ", +" @ 1.!+!+_+_+}+}+3+3+3+a+a+a+a+a+a+a+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+e.B q+q+s+u+t+w+A+A+A+A+A+A+A+A+A+A+A+A+w+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+j+f+0+5+[+(+'+Z.P.L.u.j.b.5.5.8 :.3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+}+_+_+!+!+-+.+.+X.N.H.x.k.9.9.<./.{.!.>.O @ ", +" F E !+!+!+!+_+}+}+3+3+3+7+a+a+a+7+7+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+S.1 q+q+s+s+t+t+w+A+A+A+A+A+A+A+A+A+A+w+t+w+w+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+s+u+s+q+o+j+f+0+5+(+(+@+Z.R.z.u.j.b.5.5., s.3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+}+_+!+!+!+-+.+.+T.H.w.r.k.9.6././.!.>.>.7 j ", +" $ .+;+!+!+_+_+}+3+3+3+3+7+a+a+a+a+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+~+@ f+q+q+s+t+u+t+w+A+A+A+A+A+A+A+A+w+w+t+w+t+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+u+s+s+q+q+o+j+f+f+5+(+'+@+V.L.u.j.j.b.5.^.@ ~+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+}+_+!+!+-+.+.+.+N.F.x.r.k.6.1././.!.>.$.$ ", +" $ $.-+!+!+!+!+_+}+3+3+3+3+7+7+a+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+c+. V.o+q+q+s+t+t+w+A+A+A+A+A+A+A+A+w+w+w+w+t+t+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+u+s+s+q+q+o+j+f+0+5+(+'+@+P.L.u.j.j.5.5.+.. -+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+_+!+!+!+-+.+.+T.H.x.x.k.9.6.<./.{.>.$.E $ ", +" @.[ -+-+!+!+!+_+}+3+3+3+3+3+7+a+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+4+~+S.G.G.S.S.~+}+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+e t.`.`.D.`.2+n+u+A+A+A+A+A+A+A+A+w+w+w+t+t+u+t+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+u+u+s+q+q+q+o+f+0+[+(+(+'+Z.R.L.u.j.b.5.5.U , 3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+_+_+!+!+!+-+.+.+N.F.w.r.k.9.1././.!.>.$., z ", +" @ 6.-+-+!+!+!+_+}+3+3+3+3+7+7+3+3+3+3+7+3+3+3+3+3+3+3+}+~+:._ . , 7 q q h / . @ P S.c+3+3+3+3+3+3+3+3+3+3+4+G.B . @ c h q p c $ . e D.s+A+w+w+w+w+w+t+t+t+u+t+t+t+t+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+u+u+u+s+s+q+q+o+j+0+5+5+(+'+@+V.L.z.u.f.b.5.5.B G 3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+}+}+_+!+!+;+-+.+T.N.H.x.r.9.1.<./.{.!.$.O @ ", +" z y .+-+-+!+!+!+_+}+3+3+3+3+3+3+3+7+3+3+3+3+3+3+3+3+4+P # y <..+a+a+3+3+}+_+.+2.O ) , G.}+3+3+3+3+3+3+c+e.@ 7 .H.3+a+7+3+}+_+!+w. .h @ ,.x+w+w+w+t+t+w+t+u+t+t+t+t+t+t+t+t+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+t+u+u+u+u+u+s+s+q+o+o+f+0+5+5+(+'+@+P.L.u.j.j.5.5.|.f *.3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+}+_+!+!+!+-+.+X.N.H.A.r.k.6.1.<./.!.>.$.7 a ", +" @ A..+$+-+!+!+!+_+}+3+3+3+3+3+3+7+7+3+3+3+3+3+3+G.@ E -+l+h+b+a+a+7+3+}+_+!+-+.+.+ .) B 4+3+3+3+3+4+1 7 a.l+h+b+b+a+7+3+}+_+!+!+$+.+a.p $ 2+w+t+t+t+t+t+t+u+t+t+t+t+t+t+t+t+t+t+t+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+t+u+u+t+t+u+u+u+s+s+s+q+j+j+j+0+0+5+[+'+@+V.R.z.u.j.j.5.5.|.1 7.3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+}+_+_+!+!+!+-+.+T.H.H.x.r.k.6.<./.!.>.$.V @ ", +" 3 R .+$+-+!+!+!+_+}+3+3+3+3+3+7+7+3+3+3+3+3+3+;., p.k+h+h+l+h+b+a+7+3+}+_+!+-+.+.+T.w.d / {+3+3+~+$ R h+k+h+l+h+b+a+a+3+}+_+!+!+.+.+X.N.R @ `.t+u+t+t+u+s+u+t+s+u+t+t+t+t+t+t+t+t+t+t+t+t+t+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+t+t+u+u+t+t+u+u+t+t+s+s+s+s+q+q+j+j+f+f+0+5+5+(+'+Z.V.R.u.j.j.b.5.|.^.@ K.3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+_+_+_+!+!+-+.+.+T.H.H.r.k.9.1././.!.>.$.t { ", +" - T..+$+-+!+!+_+_+}+3+3+3+3+3+3+3+3+3+3+3+S.- X.k+k+k+h+h+h+b+a+a+3+}+_+!+-+.+X.N.H.A.q p }+4+, .k+k+k+h+h+h+h+b+a+3+}+}+!+!+.+X.T.N.H. .@ n+u+u+u+s+s+s+s+s+u+u+s+s+u+t+t+t+t+u+t+t+t+t+t+t+t+t+t+t+t+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+t+t+t+t+u+u+t+t+s+s+s+t+t+s+s+s+s+q+q+s+q+q+o+j+f+f+0+0+[+(+'+@+V.P.L.u.j.j.5.5.|.+.. {+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+}+_+_+!+!+;+.+.+X.N.H.w.r.k.6.<./.!.>.>. .$ ", +" - ..+.+-+;+!+!+!+_+}+3+3+3+3+7+7+3+3+3+c+# 2.k+k+k+k+k+k+h+h+b+a+7+3+}+!+-+.+X.H.H.H.k., e.;.y l+k+k+k+k+k+h+l+b+b+7+}+}+!+;+.+X.N.H.H.A.E n s+s+s+s+s+s+s+s+s+s+s+s+u+u+s+s+s+t+t+t+t+t+s+s+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+t+t+t+t+t+t+t+u+u+t+t+t+s+s+s+s+t+t+u+s+s+s+s+q+q+s+s+q+q+q+o+o+j+f+f+0+0+[+(+'+@+Z.P.L.z.j.f.b.5.|.|.J 1 3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+_+_+_+!+!+-+.+.+T.F.w.r.k.9.1././.!.>.$.t $ ", +" %._ X..+.+-+-+!+!+_+}+3+3+3+3+3+3+3+3+3+G.} b+k+k+k+k+l+k+k+l+h+a+7+3+}+!+-+.+T.H.H.A.w.O 1 $ H.h+k+k+k+l+l+k+k+h+b+a+3+}+!+-+.+X.N.H.A.w.6.@ 2+s+q+q+q+q+q+s+q+q+q+s+s+s+s+s+s+u+s+s+s+s+s+t+t+t+t+t+t+s+s+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+u+u+t+t+t+t+t+t+s+s+s+s+t+t+t+u+s+s+s+s+s+s+q+s+s+q+q+q+q+q+o+o+j+j+f+0+0+0+5+5+(+'+@+Z.V.R.z.u.j.b.b.5.|.|.s T 3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+}+}+_+_+!+;+.+.+X.N.H.x.r.k.6.1./.{.>.$.$.- ", +" $ $..+.+-+;+!+!+_+}+3+3+3+3+3+3+3+3+3+B $.l+h+k+k+l+l+l+l+k+h+b+a+3+_+!+-+X.N.H.A.w.p.2.@ B l+l+k+k+k+l+l+k+k+l+h+a+3+}+!+-+.+X.H.A.w.w.p.q ,.o+j+o+q+o+o+q+q+q+q+q+q+q+q+q+q+s+q+s+s+s+s+u+s+s+s+s+s+s+t+t+t+t+t+t+t+t+t+s+s+s+s+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+s+s+s+s+s+s+t+t+t+t+t+t+u+s+s+s+s+s+s+s+q+s+s+s+q+q+q+q+q+q+q+o+o+j+j+f+f+f+f+0+5+5+(+(+(+'+@+V.R.L.u.j.j.b.5.|.^.^.1 :.3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+}+_+!+!+!+-+.+.+T.H.H.r.k.9.6.<./.!.>.$.H @ ", +" Y [ .+.+.+-+;+!+!+_+}+3+3+3+3+3+3+3+3+- k.h+h+k+k+l+l+l+l+k+l+b+a+3+}+!+-+X.N.H.w.w.p.g./ P h+l+h+k+l+l+l+l+k+h+h+a+3+}+!+-+.+T.H.A.w.p.g.O v j+f+j+j+j+j+j+o+o+q+q+q+q+q+q+q+q+q+q+q+s+s+s+q+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+u+s+s+s+s+s+s+s+s+s+s+s+q+q+s+s+s+s+q+q+q+q+q+q+q+q+o+o+o+j+j+j+j+f+f+f+0+0+0+5+5+[+(+'+'+@+V.R.L.z.u.f.b.5.5.|.^.^.$ s.3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+}+_+_+!+!+;+.+.+X.N.H.w.r.k.9.1./.{.>.$.$., w ", +" @ {..+.+.+-+!+!+!+_+}+3+3+3+3+3+3+c+. N.h+l+h+k+l+l+l+l+l+k+h+a+3+}+!+$+T.N.A.w.n.g.a.7 W h+h+h+k+l+l+l+l+l+k+h+b+7+}+!+-+.+N.H.A.p.g.a.O | f+f+f+f+f+f+j+j+j+j+j+j+o+o+o+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+s+s+s+s+s+s+s+s+s+s+q+s+s+s+s+s+s+s+s+s+s+s+q+q+q+q+s+s+s+s+s+s+s+q+q+q+q+q+q+q+q+q+o+q+q+q+o+o+j+j+j+j+j+f+f+f+f+f+0+0+0+0+[+[+(+(+'+@+@+V.R.L.z.u.j.b.5.5.|.^.|.U @ }+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+}+_+_+_+!+!+-+.+.+N.H.x.r.k.9.1.<./.!.>.$.H + ", +" N 7 C.C.C.C.J.O.O.O.Y.Y.Y.#+#+#+#+^+. X.b+h+l+k+l+l+l+l+l+k+h+a+3+_+!+$+T.H.A.w.g.a.a.7 W b+h+l+k+k+l+l+l+l+k+h+b+7+}+!+$+X.N.A.w.n.g.a.R | 0+0+0+0+0+0+f+f+f+f+f+f+f+j+j+j+j+j+j+j+o+o+o+o+q+q+o+q+q+q+q+q+q+q+q+q+q+q+q+s+s+s+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+o+q+q+j+j+o+j+f+f+f+f+f+f+f+f+f+0+0+0+0+[+5+[+[+(+(+'+@+@+V.P.L.L.u.u.j.b.5.5.|.^.^.^.s y #+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+Y.Y.Y.Y.Y.Y.W.O.O.O.C.C.E.o.o.l.4._.'.'.].#.......) r ", +" @ '.v.C.J.J.J.O.O.Y.Y.Y.Y.Y.Y. +&+. T.b+h+l+h+k+l+l+l+l+k+h+a+3+_+!+.+N.H.w.p.a.a.9.7 P b+b+h+h+k+l+l+l+l+k+l+b+3+}+!+$+X.N.A.w.g.g.9.O _ 5+5+5+[+[+5+0+0+0+0+0+f+f+f+f+f+f+f+j+j+j+j+j+j+o+o+o+o+q+o+o+o+q+q+o+o+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+o+q+q+q+o+j+j+o+o+j+j+j+j+j+f+f+f+f+f+f+f+f+0+0+0+0+5+5+5+5+[+(+(+(+(+'+@+@+Z.V.R.L.z.u.u.j.b.5.5.5.|.|.|.^._ <.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.W.O.O.J.C.v.v.m.d.4._._.'.'.#.#...X L @ ", +" =.U *.T G P ` -.s.U.Y.Y. +Y.Y. +&+. T.b+b+h+l+k+l+l+l+l+k+h+a+3+_+!+.+N.H.w.g.a.9.6.7 P a+b+h+l+k+l+l+l+l+k+l+b+3+}+!+$+X.H.A.p.g.a.2.O _ (+(+(+(+(+5+[+2+M.[.Z Q Q Z [.M.1+f+f+f+f+f+f+f+f+j+f+j+j+j+j+j+j+j+j+o+j+o+o+q+o+j+j+j+o+o+q+q+q+q+q+q+q+q+o+j+j+j+j+j+j+o+j+j+j+f+f+j+f+f+f+f+f+f+0+0+0+0+0+0+0+0+5+5+5+[+[+[+(+(+'+'+'+'+@+@+V.V.P.L.L.z.u.j.j.b.5.5.5.|.|.|.^.+.+ E.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. +Y.Y.Y.Y.Y.W.O.O.J.J.C.v.m.d.4.4._.'.'.'.#.#.....~ o ", +" S $ $ h H V . .O E [ . [ 7.&+Y.Y.Y.&+. N.a+b+b+h+k+l+l+l+l+h+b+a+3+}+!+.+N.H.w.g.a.2.1.7 P a+a+b+h+h+k+l+l+l+k+l+b+3+}+!+.+X.H.w.g.g.a.6.O _ '+'+'+'+2+[.{ $ 7 H V . .R E c @ _ [.1+0+0+0+f+f+f+f+f+f+f+f+f+f+f+j+f+f+f+j+j+j+f+f+f+f+j+j+j+j+j+j+j+j+j+f+f+f+f+f+f+f+f+f+f+f+f+f+f+0+0+f+0+0+0+0+0+5+5+5+5+5+[+[+(+(+(+(+(+'+'+@+@+@+@+V.P.R.R.z.u.u.u.j.f.b.5.5.5.|.^.^.^.^.s h Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.O.O.O.J.C.v.v.m.d.4._._.'.'.#.#.....L + ", +" K @ E a.3+b+a+7+3+}+}+!+!+H. .7 $ 7.Y.Y.&+. N.a+a+b+h+l+k+l+l+k+h+b+a+3+_+-+X.H.A.p.g.9.6.2.7 P a+a+b+b+h+k+k+l+k+h+h+a+3+}+!+.+N.H.w.g.a.9.2.O _ @+@+>+,.@ y 1.3+b+a+7+3+}+}+_+!+T.$.h + [.[+5+0+0+0+0+0+0+0+0+0+0+f+0+0+0+0+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+0+0+f+f+f+f+f+0+0+0+0+0+0+0+0+5+5+5+[+[+5+5+5+[+(+(+(+(+'+'+'+'+'+'+@+@+@+Z.V.R.P.L.L.z.u.u.j.j.j.b.5.5.5.|.|.^.^.^.^.M + p.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. +Y.Y.Y.Y.Y.W.O.O.J.C.v.v.m.d.4.4._.'.'.'.#.....X ~ o ", +" - q X.h+h+h+b+a+a+3+3+}+!+!+$+.+.+$./ G &+&+. N.7+a+b+h+h+h+k+k+k+l+b+7+}+!+-+X.H.A.p.g.6.2.<.7 P 7+a+b+b+h+l+h+k+k+l+h+a+3+_+;+.+N.A.w.g.9.6.1.O _ V.`.| p H.h+h+h+b+a+a+7+3+}+_+!+-+.+.+/.[ n 1+[+[+[+5+5+[+5+5+5+0+0+5+0+5+5+0+0+5+0+0+0+0+0+5+5+0+0+0+0+5+5+0+0+0+0+0+5+[+[+5+[+5+[+5+[+[+[+[+(+(+(+(+(+'+'+'+'+'+'+@+@+@+@+V.R.R.P.R.L.L.z.z.u.u.j.j.f.j.b.5.5.5.|.|.^.^.^.).s @ <.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.O.O.O.J.C.v.v.m.d.4._._.'.'.#.#.....A @ ", +" @ .h+k+h+l+h+b+b+a+7+3+}+_+!+$+.+X.T.p.7 7 &+. N.7+a+a+b+h+h+k+k+h+h+a+7+}+!+-+X.H.A.n.a.1.1.<.7 P 3+7+a+b+h+l+h+k+k+h+b+a+3+_+-+.+N.A.w.g.2.1.<.O _ `.- O b+h+h+l+l+h+b+a+7+3+}+_+!+-+.+X.T.w.q _ >+(+(+(+(+(+(+(+(+(+(+[+[+[+[+5+5+5+5+[+5+[+[+[+5+5+[+[+[+[+[+[+[+[+5+[+[+[+(+(+(+(+(+(+(+(+'+'+'+'+'+'+@+@+@+@+@+@+@+Z.V.V.P.R.L.L.L.z.z.u.u.u.j.j.f.b.b.5.5.5.5.|.^.^.^.^.J - [ l.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. +Y.Y.Y.Y.Y.W.O.O.J.C.v.v.m.d.4.4._._.'.#.#.#...X ~ o ", +" ^ V k+k+k+k+h+l+h+b+a+7+3+}+_+!+.+.+X.N.H.w.[ ` . N.7+a+a+b+h+l+h+k+h+h+a+3+}+!+-+T.H.w.g.a.6.<./.} P 3+3+a+b+b+l+h+k+k+h+b+a+}+_+;+.+N.A.p.g.2.1.<.O _ n E k+k+k+k+k+h+l+b+a+7+3+}+_+!+-+.+T.N.H.A.7 D '+'+'+'+'+'+'+'+'+'+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+'+'+(+(+(+(+(+(+'+'+(+'+'+'+'+'+'+@+'+@+@+@+@+@+@+@+Z.Z.V.V.V.P.R.L.L.L.L.z.z.z.u.u.u.j.j.f.j.b.5.5.5.|.|.|.^.^.^.).C - - !.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. +Y.Y.Y.Y.Y.Y.O.O.O.J.C.v.v.d.d.4._._.'.'.#.#.....A + ", +" ~.7 b+k+k+k+k+k+k+h+h+b+a+3+}+_+!+.+.+T.H.A.w.<.@ 6 H.3+7+a+a+b+h+l+h+l+b+a+3+}+!+$+T.H.w.g.a.1.<./.} P 3+3+a+a+b+h+l+h+l+h+b+7+}+!+-+X.H.A.n.a.2.2.<.O ( / 3+k+k+k+k+k+k+h+h+b+a+3+}+_+!+-+.+N.N.H.A.9.$ B.@+@+@+@+@+@+@+@+@+@+'+'+'+@+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+@+@+@+@+@+@+@+@+@+@+@+@+Z.Z.@+V.V.V.P.R.R.R.L.L.L.z.z.z.u.u.u.u.j.j.j.f.b.b.5.5.5.5.5.^.^.|.^.^.U f $ / <.O.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. +Y.Y.Y.W.O.O.J.C.C.v.m.d.4.4._._.'.#.#.#...X ) j ", +" - <.h+h+k+k+l+l+k+k+l+b+a+3+}+_+!+.+X.N.H.A.w.w.q j H.3+3+7+a+a+b+b+b+b+a+3+}+_+-+.+N.A.w.g.9.<.<./.} P 3+3+a+a+a+b+b+b+b+a+a+3+_+!+$+T.H.w.g.9.6.<./.O # $.h+h+k+k+l+l+k+k+l+b+a+7+}+_+!+.+X.N.H.A.w.w.y I P.R.V.V.V.Z.Z.Z.V.Z.Z.Z.Z.@+@+@+@+@+@+Z.@+@+@+@+@+@+@+@+@+@+Z.Z.Z.@+V.V.V.V.V.P.P.P.P.P.P.R.L.L.L.z.z.z.z.u.u.u.u.u.u.j.j.j.j.b.b.b.5.5.5.5.5.|.|.|.^.^.M c $ @ H p.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. +Y.Y. +Y.Y.Y.O.O.J.J.C.v.v.d.d.4._._.'.'.#.#.#...A @ ", +" + 3+l+h+k+l+l+l+l+k+k+h+b+7+}+_+!+.+T.N.H.A.p.g.V $ H.3+3+7+a+b+b+b+b+b+a+3+}+_+-+.+N.A.w.g.2.<.<./.[ P }+3+7+a+a+b+b+b+b+a+a+3+_+!+$+T.H.w.g.9.1.<./.H - .+l+h+k+l+l+l+l+l+k+h+b+a+}+_+!+.+X.N.H.A.w.n.$.@ L.L.L.L.L.L.R.R.R.P.P.R.P.P.P.P.P.V.P.P.V.V.R.P.R.R.R.P.R.P.R.R.R.R.L.L.L.L.L.L.L.L.L.z.z.u.u.u.u.u.u.u.u.j.j.j.f.j.j.j.b.b.5.5.5.5.5.5.5.|.|.^.).M 8 $ $ O 0.O.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. +Y.Y.Y.W.O.J.J.J.v.v.m.d.4.4._._.'.#.#.#...X ) o ", +" , h+h+h+k+l+l+l+l+l+k+h+b+a+3+_+!+.+N.N.H.w.g.g.{.. K.3+3+7+a+a+b+b+b+b+a+3+}+_+-+.+N.A.w.g.6.<.<./.[ P }+}+3+7+a+b+b+b+b+a+a+3+_+!+$+T.H.w.g.9.1.<./.H - a+h+l+k+l+l+l+l+l+k+l+b+a+3+_+!+.+X.N.H.w.p.g.1.. B.z.z.z.z.z.z.z.L.L.L.z.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.z.z.L.L.L.z.z.z.z.z.z.z.z.u.u.u.u.u.u.j.j.j.j.j.j.j.j.j.b.b.b.5.5.5.5.5.5.5.5.5.|.).U s / + , O 0.O.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. +Y.Y. +Y.Y.Y.O.O.J.J.C.v.v.m.d.4._._.'.'.#.#...X A @ ", +" 7 b+h+l+k+k+l+l+l+l+l+l+b+a+3+_+!+.+N.H.A.p.g.a./.. s.}+}+3+3+7+a+a+b+a+a+3+}+!+-+.+N.w.w.a.1.<./.{.[ P }+}+3+3+7+a+b+b+b+a+7+}+!+;+.+N.A.w.g.2.1.<./.H - b+h+h+k+k+l+l+l+l+l+h+b+a+3+_+;+.+T.N.A.p.g.g.2.. t.u.u.u.u.u.u.u.u.u.u.z.z.u.z.z.z.z.z.z.z.u.z.z.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.j.j.j.j.j.j.j.j.j.j.b.b.b.b.5.5.5.5.5.5.5.5.|.5.^.+.J f - . $ G 6.E.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. +Y.Y.Y.W.O.J.J.J.v.v.m.d.8.4._.'.'.#.#.....X , z ", +" h b+b+h+h+k+l+l+l+l+l+h+b+a+3+_+!+.+N.H.A.n.a.a./.. s.}+}+3+3+7+a+a+a+a+7+}+_+!+.+X.H.w.n.9.1.<./.{.[ P }+}+3+3+7+a+a+a+a+7+3+}+!+-+.+N.A.p.a.6.1././.H - b+b+h+h+k+l+l+l+l+l+h+b+a+3+_+-+.+N.H.A.n.g.a.6.. b.j.j.j.j.j.j.j.u.u.j.j.j.j.j.j.j.j.j.j.u.u.u.j.j.j.j.j.u.j.j.j.j.j.j.f.f.j.j.j.j.j.b.b.b.b.5.b.5.5.5.5.5.5.5.5.5.5.).W J n | $ . _ Q [.t.M 7 Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. +Y.Y. +Y.Y.W.O.O.J.J.C.v.v.m.d.4._._.'.'.#.#.....u $ ", +" h a+b+b+l+k+l+l+l+l+l+h+b+a+3+_+!+.+H.A.w.g.a.6./.. s.}+}+3+3+7+a+a+a+7+3+}+_+!+.+X.H.w.n.9.1.<./.{.[ P }+}+3+3+7+7+7+a+7+3+3+_+!+$+.+H.w.n.a.6.1.<./.H - a+b+b+l+k+l+l+l+l+l+h+b+a+3+_+-+.+N.A.w.g.a.a.6.. b.f.j.f.j.j.j.j.f.j.f.j.j.j.f.j.f.j.f.j.j.j.j.j.j.j.j.j.j.b.b.j.b.b.b.b.b.5.5.b.5.5.5.5.5.5.5.5.5.5.5.^.&.U J s 8 # . @ e Z b.B.R.P.L.z.z.5.. C.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. +Y.Y.Y.Y.Y.Y.O.O.O.J.C.v.v.m.d.8._._.'.'.#.#.#...X , F ", +" h a+b+b+h+h+k+l+l+l+l+h+b+a+}+!+-+X.H.A.w.g.6.2.{.. s.}+}+}+3+3+7+7+a+7+3+}+_+;+.+X.H.w.n.9.2././.{.[ P _+}+}+3+3+7+7+a+7+3+3+_+!+$+.+H.w.n.a.6.<././.H - a+b+b+h+h+k+l+l+l+l+l+b+a+3+!+-+.+H.A.w.g.a.9.1.. z n C U +.).^.5.b.b.b.b.j.j.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.5.5.b.5.5.5.5.5.5.5.5.5.5.|.^.&.W M C c 1 - . . $ e U [.`.1+[+5+[+(+@+@+V.L.L.u.u.1 /.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. +Y.Y.Y.W.O.O.J.J.C.v.v.d.4.4._._.'.#.#.#...X u $ ", +" 7 7+a+b+b+l+k+k+l+l+k+l+a+7+}+!+$+X.H.A.w.a.6.6.!.. s.}+}+}+3+3+7+7+a+7+3+}+!+-+.+X.H.w.n.9.2././.{.[ P _+}+}+3+3+7+7+a+7+3+3+_+!+$+.+H.w.n.a.6.<././.H - 7+a+b+b+l+k+l+l+l+k+h+b+a+}+!+-+X.H.A.w.g.9.6.<.. i.,.I | @ . . . # _ 8 f s B J M U U W +.+.&.&.&.&.&.&.&.&.+.+.W U M M J C s f 1 - $ . . . @ | I ,.t.`.1+o+s+s+q+q+j+f+f+5+[+(+'+@+V.L.L.z.z.M 7 Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. +Y.Y. +Y.Y.Y.W.O.O.J.C.v.v.m.d.4.4._.'.'.#.#.....X $ ", +" 7 7+a+a+b+h+h+k+k+l+k+h+a+7+}+!+$+T.H.A.n.9.2.2.>.. s._+}+}+}+3+3+3+3+3+}+_+!+-+.+N.A.p.g.6.1././.!.[ P _+_+}+}+3+3+3+3+3+3+}+!+-+.+X.H.w.g.a.1.<./.{.H - 7+a+a+b+h+l+k+l+l+k+l+b+7+}+!+$+T.H.A.n.a.6.1./.. 1+u+u+u+u+q+j+1+>+R.B.[.,.U a 1 1 , $ . . . . . . . . . . . . @ # 1 8 c C P T J w >+1+f+q+u+u+u+u+t+t+t+s+s+s+q+q+j+f+f+0+5+(+'+@+Z.P.L.z.u.5.. C.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. +Y.Y.Y.W.O.O.J.J.v.v.m.d.d.4._._.'.'.#.#.....i $ ", +" 7 3+a+a+a+b+l+k+k+k+h+b+a+7+}+!+.+N.H.w.g.9.1.<.>.. s._+_+_+}+}+3+3+3+}+}+!+!+$+X.N.A.n.g.6.<././.!.[ P _+_+_+}+}+}+3+3+}+}+_+!+$+.+T.H.w.g.9.1././.{.H - 3+a+a+a+b+h+h+k+k+h+h+a+7+}+!+.+T.H.w.n.9.1.1./.. 1+u+u+u+u+u+u+u+u+u+u+u+u+u+Q .}+}+3+7+}+}+c+c+}+}+}+-+-+$+.+N.A.w.g.9.2././.H e u+u+u+u+u+u+u+u+t+t+t+s+u+s+q+q+j+j+f+0+[+[+(+@+@+V.L.L.u.u.1 /.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. +Y.Y. +Y.Y.Y.O.O.O.J.C.v.v.m.d.4._._.'.'.#.#.....X $ ", +" 7 3+7+a+a+b+h+l+h+l+h+b+a+3+_+-+.+N.A.w.g.9.<./.$.. s._+_+_+_+}+}+}+}+}+_+!+-+.+T.H.w.g.a.1.<./.!.>.[ P !+!+_+_+}+}+}+}+}+_+!+;+.+X.N.A.n.a.2.1./././.H - 3+7+a+a+b+h+h+l+l+h+b+a+3+_+-+.+N.H.w.g.9.1.<./.. 1+u+u+u+u+u+u+u+u+u+u+u+u+u+Q .}+}+3+7+a+a+b+b+b+a+7+}+!+;+.+N.A.w.g.9.2././.H e u+u+u+u+u+u+u+u+t+t+t+t+t+s+q+q+o+j+f+0+5+5+(+'+@+V.L.L.z.u.M 7 Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y. +Y.Y.Y.W.O.O.J.C.v.v.m.d.4.4._._.'.#.#.#.....i { ", +" 7 }+3+7+a+a+b+h+h+h+b+a+7+3+_+-+.+N.A.p.g.6.<./.$.. s.!+!+!+_+_+_+_+_+!+!+$+.+X.N.H.w.g.a.2.<.{.!.!.[ P !+!+!+!+_+_+_+_+_+!+-+.+X.N.H.w.g.a.1.1./.!.!.H - }+3+7+a+a+b+b+h+h+b+a+7+3+_+-+.+N.A.w.g.6.<.<.{.. 1+u+u+u+u+u+u+u+u+u+u+u+u+u+Q .}+}+3+7+a+a+b+b+b+a+7+}+!+;+.+N.A.w.g.9.2././.H e u+u+u+u+u+u+u+u+t+t+u+t+s+s+q+q+q+j+j+f+0+5+(+'+@+V.P.L.L.u.|.. $+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+&+&+&+&+&+U.U.H.H.l.p.0.0.].].]...$.$.R @ ", +" 7 }+3+3+7+a+b+b+b+b+b+a+3+}+!+$+X.H.w.n.a.1.<./.$.. s.!+!+!+!+!+!+!+!+!+-+.+X.N.H.A.n.g.9.1./.!.!.!.[ P !+!+!+!+!+!+!+!+!+-+$+.+N.H.A.w.g.9.1.<./.!.!.H - }+3+3+7+a+b+b+b+b+b+a+3+}+!+$+.+N.A.p.a.1.<.<.{.. 1+u+u+u+u+u+u+u+u+u+u+u+u+u+Q .}+}+3+7+a+a+b+b+b+a+7+}+!+;+.+N.A.w.g.9.2././.H e u+u+u+u+u+u+u+t+u+t+u+t+s+s+s+q+q+o+j+f+0+5+[+(+@+Z.V.R.L.z.u.1 e.3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+}+_+_+!+!+-+.+.+X.N.x.x.r.9.6.1./.{.>.$.$.} 3 ", +" 7 }+}+3+3+a+a+a+b+b+a+7+3+}+!+.+X.H.w.g.a.1.<./.$.. s.-+;+;+;+;+;+;+!+;+$+.+X.N.H.w.g.a.6.<./.!.!.>.[ P !+;+!+!+;+;+;+!+;+-+.+X.N.H.A.w.a.6.1./.{.!.>.H - }+3+3+3+a+a+a+b+b+a+7+3+}+!+.+X.N.A.n.a.1.1.<.!.. 1+t+u+u+u+u+u+u+u+u+u+u+u+u+Q .}+}+3+7+a+a+b+b+b+a+7+}+!+;+.+N.A.w.g.9.2././.H e u+u+u+u+u+u+u+t+u+t+u+t+s+u+s+s+q+o+j+f+0+0+5+(+'+@+V.R.L.z.u.M c 3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+}+}+_+!+!+;+-+.+X.N.H.w.r.k.9.1./.{.!.>.$.O . ", +" 7 _+}+}+3+7+a+a+a+a+a+3+}+_+!+.+T.H.p.g.9.2./.{.$.. s.-+$+$+-+-+-+$+.+.+.+X.N.H.A.p.a.9.6.<./.!.!.>.[ P -+-+-+-+-+$+-+-+.+.+X.N.H.A.w.g.9.1.1./.!.>.>.H - _+}+}+3+3+a+a+a+a+a+3+}+_+!+.+T.H.w.g.9.2.<./.>.. 1+u+t+t+t+t+t+t+t+t+t+t+t+t+Q .}+}+3+7+a+a+b+b+b+a+7+}+!+;+.+N.A.w.g.9.2././.H f t+t+t+t+t+t+t+u+t+u+u+t+t+t+s+s+q+q+o+f+f+0+[+(+'+@+V.P.L.z.u.|.. {+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+}+3+}+}+_+!+!+!+;+-+.+T.H.F.x.r.k.6.<./.!.>.>.$./ j ", +" 7 _+}+}+3+3+7+a+a+7+7+3+}+_+-+.+N.A.n.g.9.1./.!. .. s..+.+.+.+.+.+.+X.X.T.N.H.A.w.g.9.6.1./.{.>.>.>.[ P .+.+.+.+.+.+.+.+.+T.N.H.A.w.g.a.6.2.<./.!.!.!.H - _+}+}+3+3+3+7+7+7+7+3+}+_+-+.+N.H.w.g.9.1././.>.. 1+t+t+t+t+t+t+t+t+t+t+t+t+t+Q .}+}+3+7+a+a+a+a+a+7+3+}+!+-+.+N.A.w.g.9.2././.H f t+t+t+t+t+t+t+t+t+u+u+t+t+t+s+s+q+q+o+j+f+0+5+[+(+'+Z.V.R.z.z.u.1 e.3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+}+}+_+!+!+!+-+.+X.N.H.w.r.k.9.1././.!.>.$.H @ ", +" 7 _+_+}+}+}+3+3+3+3+3+}+_+!+$+X.N.A.g.g.6.<.{.!. .. 7..+.+.+.+.+X.X.T.N.H.H.A.w.n.g.6.1.<./.{.>.>.>.[ J .+.+.+.+.+X.X.T.N.N.H.A.w.p.g.a.1.<./.!.>.!.>.H - _+_+}+}+}+3+3+3+3+3+}+_+!+$+X.N.A.p.g.6.<./.{.>.. 1+u+u+u+u+u+u+u+u+u+u+u+u+u+Q .}+}+3+7+7+a+a+a+a+7+3+}+!+-+.+N.A.w.g.9.2././.H e u+u+u+u+u+u+u+u+u+u+u+u+t+s+s+s+s+s+q+j+f+f+0+5+(+'+@+V.R.L.z.u.J h 3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+}+}+}+_+!+!+-+.+.+N.H.F.x.k.9.6.<./.{.>.$.$., w ", +" 7 _+_+_+}+}+}+3+3+3+}+_+!+!+.+T.H.w.g.a.6./.{.!. .. >..+.+X.T.T.T.N.N.H.A.w.w.p.g.9.1.1./.!.!.>.>.>., B .+.+X.T.T.N.N.H.H.A.A.w.n.g.a.6.<./.{.!.!.>.>.t 3 !+_+_+}+}+}+3+3+3+}+_+!+!+.+T.H.A.n.a.6./././.>.. 1+u+u+u+u+u+u+u+u+u+u+u+u+u+Q .}+}+3+7+7+a+b+b+b+a+7+}+!+;+.+N.A.w.g.a.2././.H e u+u+u+u+u+u+u+u+u+u+u+u+t+s+u+u+s+q+q+o+f+f+0+[+(+'+@+Z.P.L.L.u.|.. {+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+}+3+3+}+_+_+_+!+;+.+.+T.N.H.x.r.k.9.1./.{.!.$.$.E $ ", +" 7 !+_+_+_+_+}+}+}+}+}+!+!+-+.+N.H.w.a.a.1./.{.!. .. T X.T.N.N.H.H.H.A.A.w.w.n.g.9.1.1./.{.!.!.>.>. .. e N.T.N.N.N.H.H.H.A.w.w.n.g.a.2.1././.{.!.>.>.>.[ j !+_+_+_+_+}+}+}+}+}+!+!+-+.+N.H.w.g.a.1./././.>.. 1+u+u+u+u+u+u+u+u+u+u+u+u+u+Q .}+}+3+7+a+a+b+b+b+a+7+}+!+;+.+N.A.w.g.a.1././.H e u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+u+s+q+q+o+j+f+0+5+(+'+'+@+P.R.L.z.u.| e.3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+}+}+_+_+!+!+.+.+.+N.H.F.r.k.9.6././.!.>.$. .$ ", +" 7 !+!+!+!+!+_+_+_+!+!+-+$+.+T.H.A.n.a.6.1./.{.!. .. | H.N.H.H.H.A.A.w.w.p.g.a.6.1.1.<./.!.>.>.>.>.i o $ $.N.N.H.H.H.A.w.w.w.n.g.a.2.2.<././.!.>.>.>.V . e !+!+!+!+!+_+_+_+!+!+-+$+.+T.H.A.p.a.6.1././.!.>.. 1+u+u+u+u+u+u+u+u+u+u+u+u+u+Q .}+3+3+7+a+a+b+b+b+a+7+}+!+;+.+N.A.w.g.9.6.<./.H e u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+q+q+j+f+f+0+[+(+'+@+V.P.L.z.u.J h 3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+3+}+_+_+!+!+-+.+.+T.N.H.x.k.9.9.1./.{.!.$.$.i - ", +" 7 !+!+!+!+!+!+!+!+!+;+$+.+X.N.A.w.n.a.6.<./.!.>. .. j H N.H.H.A.w.w.w.n.g.a.6.6.2.<././.!.>.>.>.V $ 3.~./ w.H.H.A.A.w.w.p.g.a.9.6.1.<././.{.>.>.>.$.[ F _ !+!+!+!+!+!+!+!+!+;+$+.+X.N.A.w.n.a.2.<./.{.!.>.. 1+u+u+u+u+u+u+u+u+u+u+u+u+u+Q .}+3+7+a+a+a+b+b+b+a+7+}+!+;+.+N.A.w.g.a.2.<./.H e u+u+u+u+u+u+u+u+u+u+u+u+u+t+u+u+u+s+q+q+o+f+f+0+5+(+'+@+Z.P.L.z.z.|.. {+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+}+}+_+!+!+;+-+.+.+N.H.x.r.k.9.1././.!.>.$.V @ ", +" 7 -+;+-+;+;+;+!+;+-+$+.+.+T.H.A.p.g.2.1.<.{.>.>. .. q.@ .H.A.w.w.p.n.g.a.6.1.2.1././.{.!.>.>. ., S y.y.o q w.A.A.w.p.n.g.a.9.6.2.<././.{.!.>.>.>.d - Q._ -+;+-+;+;+-+-+-+-+$+.+.+T.H.A.w.n.a.1.<.{.!.!.>.. 1+u+u+u+u+u+u+u+u+u+u+u+u+u+Q .}+}+7+a+a+a+b+b+b+a+7+}+!+;+.+N.A.w.g.a.6././.H e u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+u+s+s+q+q+j+f+0+5+(+'+'+@+P.L.L.z.u.| e.3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+}+}+_+!+!+!+;+-+.+T.N.F.x.k.9.6.1./.{.>.$.$.7 ( ", +" 7 $+$+$+$+-+$+$+.+.+.+T.N.H.A.w.n.a.1.<./.!.>.>. .. q.h.@ H w.w.p.g.a.9.2.1.1.1./.{.{.!.!.>.R - z I.I.I.I.6 [ 2.w.p.n.g.a.9.2.2.<./././.!.!.>.$.} - Y ++_ $+$+$+$+-+$+$+$+.+.+T.N.H.A.w.p.g.6.<./.!.!.!.>.. 1+u+u+u+u+u+u+u+u+u+u+u+u+u+Q .}+}+3+a+a+b+b+b+b+a+7+}+!+;+.+T.H.w.n.a.1././.H e u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+q+q+q+j+f+0+5+[+(+'+@+V.P.L.z.u.J h 3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+}+}+_+!+!+!+-+.+X.N.H.x.r.k.9.1.<./.!.>.$.O . ", +" 7 .+.+.+.+.+.+.+X.X.T.N.H.H.w.n.g.9.<././.!.>.>. .. q.I.y.{ / V a.g.a.6.1.<.<.<./.{.{.!.V 7 + @.Q.Q.I.I.I.I.K $ E 2.g.g.a.2.1.1.<././.!.>. .E $ 3 @.Y ++, .+.+.+.+.+.+.+.+X.T.N.H.H.w.p.g.9.1.<./.{.!.>.$.. 1+u+u+u+u+u+u+u+u+u+u+u+u+u+Q .}+}+3+7+a+a+b+b+b+a+a+3+_+!+-+X.H.w.g.9.2././.H e u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+u+u+u+s+q+q+o+f+0+5+5+[+(+@+Z.P.L.z.z.|.. {+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+}+}+}+_+!+!+-+.+.+N.H.H.r.k.9.6.1./.!.>.$.$./ j ", +" , .+.+.+X.T.T.T.N.N.N.H.H.A.p.a.9.1.<././.>.>.>. .. y.I.Q.Q.(.# # t O $./.<.<.!. .O t / . z I.++Q.Q.Q.Q.I.I.y.q.a @ 7 H .!./.<.{.$.V H 7 @ { Y @.@.Y |+$ .+.+.+X.X.X.T.N.N.N.H.H.A.p.g.9.1.<././.>.>.>. .. 1+u+u+u+u+u+u+u+u+u+u+u+u+u+Q .}+}+3+7+a+a+b+b+b+a+a+3+_+!+$+T.A.w.g.9.2././.H e u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+u+s+s+q+q+f+f+0+[+[+(+'+@+P.L.L.z.u.| e.3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+}+3+3+}+_+_+!+!+!+.+.+T.N.H.w.k.9.9.1./.!.>.>.$.H @ ", +" @ T..+X.T.N.N.N.H.H.H.A.w.p.g.9.6.1./.{.!.>.>.>.O # I.Q.Q.Q.++++y.Y 3 . . . . . . $ z }.++++++++++++Q.Q.Q.I.I.y.y.q.@.j @ . . . . . @ 3 N =.=.%.@.@.Y d+. w..+X.T.T.N.N.H.H.H.A.w.p.g.9.6.1././.{.>.>.>.R . q+u+u+u+u+u+u+u+u+u+u+u+u+u+Q .}+}+3+7+a+a+b+b+b+a+7+}+!+;+.+N.A.w.g.9.2././.H e u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+s+s+s+q+o+j+f+f+5+[+(+'+@+V.P.L.z.u.J p 3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+}+}+_+_+!+!+-+.+.+N.H.F.r.k.9.6.<.{.!.>.$. .- z ", +" $ >.T.N.N.N.H.H.H.A.w.p.g.g.9.1.2.<.{.{.!.>.>.>.7 K Q.Q.Q.++++++++,+,+,+++Q.Q.++++,+,+,+,+,+,+++++++++Q.Q.Q.I.I.y.y.q.q.q.h.c.3.3.}.}.(.~.~.=.%.%.@.@.p+6 .T.N.N.H.H.H.A.A.w.p.g.g.a.6.2.<./.{.{.>.>.>.q v w+w+w+w+w+w+w+w+w+w+w+w+w+w+Q .}+}+3+7+a+a+b+b+b+a+7+}+!+;+.+N.A.w.g.9.2././.H e w+w+w+w+w+w+w+w+w+w+t+u+u+u+u+t+u+u+u+s+q+q+o+j+f+5+5+[+'+@+Z.P.L.z.z.^.. {+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+3+}+_+_+!+!+-+.+.+T.N.H.x.k.9.9.1./.!.>.>.$.t $ ", +" S 7 N.H.H.H.A.A.w.w.n.g.g.9.1.1.1./.!.!.!.>.$. .@ q.Q.Q.++++++,+,+,+,+,+<+<+<+<+<+<+<+<+,+,+,+,+,+++++++Q.Q.Q.I.I.y.q.q.q.h.c.c.3.3.}.(.(.~.=.%.%.@.@.p+I.1 H.N.H.H.A.A.A.w.p.g.g.a.6.1.<./.{.!.!.>.>.$.# R.w+w+w+w+w+w+w+w+w+w+w+w+w+w+Q .}+}+3+7+a+a+b+b+b+a+7+}+!+;+.+N.A.w.g.9.2././.H e w+w+w+w+w+w+w+w+w+w+t+t+t+u+u+t+t+t+u+s+q+q+q+j+f+0+5+[+'+'+@+P.L.L.z.u._ 7.3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+}+_+_+!+!+;+-+.+X.N.H.w.r.k.9.1./.{.!.>.$. .$ ", +" $ R H.H.A.w.w.p.g.g.g.a.2.1.<./.!.!.!.>.$.$.[ w Q.Q.++++++,+,+,+,+<+<+<+|+|+|+|+|+|+<+<+<+,+,+,+,+++++++Q.Q.Q.I.y.y.q.q.h.h.c.3.3.}.}.(.~.~.=.%.@.=.p+p+3 H H.H.A.A.w.p.g.g.g.a.6.1./././.!.!.>.>.>.7 e s+u+u+u+u+u+u+u+u+u+u+u+u+u+u+Q .}+}+3+7+a+a+b+b+b+a+7+}+!+;+.+N.A.w.g.9.2././.H e u+u+u+u+u+u+u+u+u+u+u+t+t+u+u+u+t+u+u+s+s+s+q+j+f+f+0+5+(+'+@+V.P.L.z.u.J p 3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+}+_+_+_+!+!+-+.+.+T.H.F.x.k.9.6.1./.!.>.$.$.7 { ", +" @ V A.w.w.p.g.g.a.2.1.1.<././.!.!.!.>.$.7 { I.Q.++++++,+,+,+<+<+<+|+|+|+|+|+|+|+|+|+|+<+<+<+,+,+,+++++++Q.Q.I.I.y.y.q.q.h.c.c.3.3.}.(.~.~.=.%.%.(.p+p+8+@ O A.w.w.n.g.g.a.2.1.1././././.!.!.>.$.d $ D.M.M.M.M.M.M.i.i.i.i.i.i.i.[.[.D .}+}+3+7+a+a+b+b+b+a+7+}+!+;+.+N.A.w.g.9.2././.H | i.i.i.i.M.M.M.M.M.M.M.`.`.`.2+2+2+2+2+n+n+n+n+n+f+f+[+[+(+'+@+Z.P.L.z.u.^.. {+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+3+}+3+3+}+_+_+_+!+!+-+.+X.N.H.w.r.k.9.1.<./.!.$.$.O . ", +" @ t a.p.g.g.g.9.6.1.<./././.{.!.!.V / 3 y.Q.++++++,+,+,+<+<+|+|+|+8+8+8+8+8+8+8+8+|+|+|+<+<+,+,+,+++++++Q.Q.I.y.y.q.q.h.h.c.3.3.}.(.(.~.=.=.%.3.p+p+p+<+- q 1.w.n.g.a.9.6.1.<./././.!.>.>.V [ - D n B B B B B B C M M M J J J J U v .}+}+3+7+a+a+b+b+b+a+7+}+!+;+.+N.A.w.g.9.2././.H _ M M M C C C C B B B n n n e | | { { - . . . . . . . . . . ( 3 e v F Q Z ,.K |.S.S.~+~+4+4+c+3+3+3+3+3+3+3+3+3+3+}+3+3+}+}+_+_+!+!+-+.+X.T.H.x.x.k.9.1.<./.!.>.$.$./ j ", +" 3 $ E !.g.a.6.1.<.<././.{.>.O 7 @ S I.Q.Q.++++,+,+,+<+<+|+|+8+8+8+8+d+d+d+d+8+8+8+8+|+|+<+<+,+,+,+++++Q.Q.I.I.y.y.q.q.h.c.c.3.}.}.(.~.~.=.%.c.p+p+p+p+d+K $ E >.a.9.2.1.<.<.<./.{.>.R h @ U j+u+t+t+t+t+u+u+t+t+t+t+u+u+t+t+t+t+Q .}+}+3+7+a+a+b+b+b+a+7+}+!+;+.+N.A.w.g.9.2././.H f t+t+t+t+u+u+t+t+t+u+s+u+t+t+s+u+t+u+s+s+u+j+f+0+(+'+@+L.z.j.b.5.^.).+.U M B v n 8 , @ . . . $ 8 C ` :.e.S.~+}+3+3+3+3+_+_+!+!+!+;+.+.+X.N.H.w.p.9.9.1.<.!.!.$.$.E @ ", +" 6 . , h E O O O H t [ $ # N q.I.Q.Q.++++++,+,+<+<+|+|+8+8+d+d+d+d+d+d+d+d+d+d+8+8+|+|+<+<+,+,+++++++Q.Q.I.y.y.q.q.h.c.c.3.3.}.(.~.~.=.%.q.r+p+p+p+p+i+,+z @ , h E O O O H t 7 - @ U 1+u+u+u+t+u+u+u+u+t+t+u+u+u+u+t+t+u+u+u+Q .}+}+3+3+7+a+a+a+a+7+3+}+!+-+.+N.A.p.g.6.1./.{.H f t+t+u+u+t+t+t+t+t+u+t+t+t+t+t+t+t+t+t+t+t+t+t+t+u+t+t+t+t+t+u+s+u+s+u+u+s+s+s+s+s+s+s+f+[+Z.u.5.).W J s 8 @ . $ p *.k.&+Y.O.O.J.J.C.v.v.d.d.4.4._.'.'.#.#.....X , z ", +" =.S r 6 ^ ( a z @.3.q.y.I.I.Q.Q.++++,+,+,+<+|+|+8+8+d+d+i+i+i+i+i+i+i+i+d+d+8+8+|+|+<+,+,+,+++++Q.Q.I.I.y.q.q.h.h.c.3.3.}.(.(.~.=.%.y.r+p+p+p+r+D+F+i+d+Q.(.K j ( ( j K (.y.1+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+Q .}+}+3+3+7+a+a+a+a+7+3+}+!+-+.+N.A.p.g.6.1./.{.H e t+t+w+w+t+t+w+t+t+w+t+t+t+t+t+u+u+t+t+u+u+u+u+u+u+t+t+t+t+t+t+u+t+t+t+t+u+t+t+s+u+s+s+s+s+q+q+q+q+o+o+j+f+0+V.5.+.J _ $ 6.O.J.J.J.v.v.m.d.4.4._.'.'.#.#.....X i - ", +" =.~.(.(.}.3.3.c.h.q.q.y.y.I.Q.Q.++++++,+,+<+|+|+8+8+d+d+i+i+p+p+p+p+p+p+i+i+d+d+8+8+|+|+<+,+,+++++++Q.Q.I.y.y.q.q.h.c.3.3.}.(.(.~.=.%.Q.r+p+y+F+G+G+F+i+i+d+d+d+8+8+8+8+8+|+|+I.A+w+A+A+A+A+w+A+A+A+A+A+w+w+A+A+A+A+A+A+A+Q .}+}+3+3+7+a+a+a+a+7+3+}+!+-+.+N.A.p.g.6.1././.H e A+w+w+w+w+w+A+A+w+w+w+w+A+w+w+w+w+w+w+w+t+w+w+t+w+t+u+t+t+t+u+u+u+u+u+t+t+u+t+t+t+t+s+s+u+s+s+q+q+o+o+j+j+f+f+0+5+5+5+^.$ k.J.J.v.v.v.d.4.4._._.'.'.#.#.....L @ ", +" %.~.~.(.}.}.3.c.c.h.q.q.y.y.I.Q.Q.++++,+,+<+<+|+8+8+d+d+i+p+p+p+p+r+r+p+p+p+p+i+d+d+8+8+|+<+,+,+,+++++Q.Q.I.y.y.q.q.h.c.c.3.}.}.(.~.=.=.,+D+G+G+G+G+G+F+i+i+d+d+d+d+8+8+8+8+|+|+|+`.A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+Q .}+}+3+3+7+a+a+a+a+7+3+}+!+-+.+N.A.p.g.6.2./.{.H e A+A+A+A+A+A+A+A+A+A+A+w+A+A+A+A+w+A+A+A+w+w+A+w+w+A+w+w+A+w+w+w+w+w+w+w+t+w+t+t+u+t+t+s+t+u+s+s+q+o+o+o+j+f+f+0+0+5+5+[+&.$ H.C.v.v.m.d.4.4._._.'.#.#.#...X } 3 ", +" =.~.~.(.}.3.3.c.c.h.q.q.y.I.I.Q.Q.++++,+,+<+|+|+8+8+d+i+p+p+p+r+r+r+r+r+r+p+p+p+i+d+8+8+|+|+<+,+,+++++Q.Q.I.I.y.q.q.h.c.c.3.3.}.(.~.3.++F+G+G+G+G+G+G+D+i+i+d+d+d+d+8+8+8+8+Q.%.o D A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+B+Q V _+}+3+3+3+7+a+a+7+3+3+_+!+$+.+N.A.p.g.6.1./.!.H f A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+w+A+A+t+w+w+t+t+t+t+t+u+s+q+o+o+j+f+f+0+0+5+5+(+(+J c E.v.m.d.4.4._._.'.#.#.#...X A + ", +" %.=.~.~.(.}.3.3.c.h.h.q.q.y.I.I.Q.++++++,+,+<+|+8+8+d+i+i+p+p+r+r+r+y+y+r+r+r+p+p+i+i+d+8+|+|+<+,+,+++++++Q.I.I.y.q.q.h.h.c.3.3.}.h.<+r+r+G+G+G+G+G+G+G+D+i+i+d+d+d+d+8+,+(.F a a 6 3 L.B+B+B+A+A+B+A+B+B+B+A+A+A+A+A+A+B+A+B+Q V _+}+}+}+3+3+3+3+3+3+}+!+-+.+X.H.w.g.a.1.<./.!.H f A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+w+t+t+t+t+u+s+q+q+o+j+f+0+0+5+5+(+(+@+1 ` v.d.d.4.4._.'.'.#.#...X X , w ", +" %.=.~.~.(.}.3.3.c.h.q.q.y.y.I.Q.Q.++++,+,+,+<+|+8+8+d+i+p+p+r+r+y+y+y+y+y+y+r+r+p+p+i+d+8+8+|+<+,+,+,+++++Q.Q.I.y.y.q.q.h.c.3.I.8+y+r+r+r+G+G+G+G+G+G+G+y+i+i+d+d+8+q.Y r o j a a 6 3 D B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+Q V _+_+}+}+3+3+3+3+3+3+}+!+-+.+X.H.w.g.a.1.<./.!.H f B+B+B+B+B+B+B+A+A+B+B+B+B+B+B+A+A+B+A+A+B+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+w+w+w+t+t+s+q+q+q+j+f+f+0+0+[+(+(+'+j.$ 1.d.4.4._.'.'.#.#.#...X i $ ", +" %.%.=.~.(.(.}.3.3.c.h.q.q.y.y.I.Q.Q.++++,+,+<+|+|+8+d+d+i+p+r+r+y+y+y+y+y+y+y+y+r+r+p+i+d+d+8+|+|+<+,+,+++++Q.Q.I.y.y.q.q.h.Q.i+y+y+y+y+r+r+G+G+G+G+G+G+G+y+i+i+,+3.K z w r o j a a 6 3 3 R.B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+Q V _+_+}+}+}+}+3+3+}+}+_+!+$+.+T.H.w.g.a.1.<.{.!.H f B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+A+A+B+B+A+A+B+A+A+B+A+A+A+A+A+A+A+A+A+A+A+A+A+w+u+t+s+s+q+o+j+f+0+0+5+(+(+'+'++.$ l.4._._.'.'.#.#...X L @ ", +" %.%.=.~.(.(.}.3.3.c.h.q.q.y.y.I.Q.Q.++++,+,+<+|+|+8+d+i+i+p+r+r+y+y+D+D+D+D+y+y+r+r+p+i+i+d+8+|+|+<+,+,+++++Q.Q.I.y.y.q.++p+y+y+y+y+y+y+r+y+G+G+G+G+G+G+G+p+I.@.K F z z w r o j a a 6 3 3 M B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+Q V !+!+_+_+}+}+}+}+}+_+!+;+.+X.N.A.p.g.9.1./.{.!.H f B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+A+B+B+B+B+A+B+B+A+A+B+A+A+B+A+A+A+A+A+A+A+A+A+A+t+t+t+s+s+q+o+f+0+0+5+(+'+'+'+@+c G 4._.'.'.#.#...X X } | ", +" %.%.=.~.(.(.}.3.3.c.h.q.q.y.y.I.Q.Q.++++,+,+<+|+|+8+d+i+p+p+r+r+y+y+D+D+D+D+y+y+r+r+p+p+i+d+8+|+|+<+,+,+++++Q.Q.I.y.,+r+y+y+y+y+y+y+y+y+r+y+G+G+G+G+G+G+y+%.N N K F F z w r o j j a 6 3 3 ( j+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+Q V !+!+!+_+_+_+_+_+_+!+!+$+.+T.H.w.n.g.2.1./.!.!.H f A+B+B+B+B+B+B+B+B+B+B+B+B+B+B+A+B+B+B+B+B+B+B+A+B+B+B+B+B+B+B+B+B+B+B+B+A+B+B+A+A+A+A+A+A+A+A+A+w+w+t+u+s+q+o+j+f+0+5+(+(+(+'+@+z.- -._.'.#.#.#...X A + ", +" @.%.%.=.~.(.(.}.3.3.c.h.q.q.y.y.I.Q.Q.++++,+,+<+|+|+8+d+i+p+p+r+r+y+y+D+D+D+D+y+y+r+r+p+p+i+d+8+|+|+<+,+,+++++Q.Q.,+r+D+D+y+y+y+y+y+y+y+y+r+D+G+G+G+F+y+p+i+S N N K F F z w r o j j a 6 3 3 ( 5.B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+Q V !+!+!+!+!+!+!+!+!+!+$+.+X.N.H.w.g.a.1.<./.!.!.H f A+B+B+B+B+B+B+B+B+B+B+B+B+B+B+A+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+B+A+A+B+B+A+A+A+A+A+A+A+A+A+A+w+t+t+t+q+o+o+f+0+0+[+(+(+'+@+@+&.@ <.'.#.#...X X , r ", +" @.%.%.=.~.(.(.}.3.3.c.h.q.q.y.y.I.Q.Q.++++,+,+<+|+|+8+d+i+i+p+r+r+y+y+D+D+D+D+y+y+r+r+p+i+i+d+8+|+|+<+,+,+++++,+r+D+D+D+D+y+y+y+y+y+y+y+y+r+D+G+F+y+p+p+p+|+S N N K F z z w r o j j a 6 3 3 ( v A+A+A+A+A+A+B+B+A+A+A+B+A+A+A+A+A+Q V -+;+;+;+!+!+;+-+-+.+.+X.N.H.A.p.a.9.2././.!.>.H f A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+B+B+A+A+A+A+A+A+A+A+B+B+A+A+A+A+B+B+A+A+B+B+A+A+B+A+A+A+A+A+A+A+A+u+t+t+s+q+q+f+0+0+5+(+(+'+@+@+Z.s c #.#.....X i $ ", +" @.%.%.=.~.(.(.}.3.3.c.h.q.q.y.y.I.Q.Q.++++,+,+<+|+|+8+d+d+i+p+r+r+y+y+y+y+y+y+y+y+r+r+p+i+d+d+8+|+|+<+,+,+<+y+D+D+D+D+D+D+y+y+y+y+y+y+y+y+r+p+y+r+r+p+p+p+++S N N K F z z w r o j a a 6 3 3 ( { (+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+Q V -+-+-+-+-+-+-+.+.+X.X.N.H.A.w.g.a.2.1././.!.>.H e A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+w+t+u+u+s+q+q+j+f+0+5+[+(+'+@+@+Z.z.^ ` ......L @ ", +" @.@.%.%.=.~.~.(.}.3.3.c.h.q.q.y.y.I.Q.Q.++++,+,+<+<+|+8+8+d+i+p+p+r+r+y+y+y+y+y+y+r+r+p+p+i+d+8+8+|+<+<+<+r+F+D+D+D+D+D+D+D+y+y+y+y+y+y+r+Q.(.<+r+r+r+p+p+p+I.S N N K F z z w r o j a a 6 3 3 ( { ).A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+Q V -+.+.+.+.+.+.+.+X.T.N.H.A.w.p.g.6.2.<./.!.>.>.H e A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+w+w+w+t+u+u+s+q+j+f+f+0+5+(+'+'+@+@+V.).+ $.....} | ", +" Y @.@.%.=.~.~.(.}.3.3.c.h.h.q.q.y.I.Q.Q.++++++,+,+<+|+8+8+d+i+i+p+r+r+r+y+y+y+y+r+r+r+p+i+i+d+8+8+|+|+p+F+F+F+D+D+D+D+D+D+D+y+y+y+y+r+++3.~.~.d+r+r+r+p+p+p+h.S N K K F z w w r o j a a 6 3 3 ( { B A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+Q V .+.+.+.+X.X.T.N.N.N.H.A.w.n.g.a.1.1./.{.!.>.>.E e A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+w+A+w+t+u+u+s+q+j+j+f+0+5+(+(+'+@+@+V.V.C 1 ..A @ ", +" Y @.@.%.=.~.~.(.}.3.3.c.c.h.q.q.y.I.I.Q.++++++,+,+<+|+|+8+d+d+i+p+p+r+r+r+r+r+r+r+r+p+p+i+d+d+8+|+p+D+F+F+F+F+D+D+D+D+D+D+D+y+y+r+++3.}.(.~.~.r+r+r+p+p+p+p+(.S N K K F z w w r o j a 6 6 3 3 ( { { s+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+M R .+X.X.T.T.N.N.H.H.A.w.p.g.g.a.2.<.<./.!.!.>.>.H e A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+w+w+t+u+s+s+s+o+o+f+0+5+[+(+(+@+@+Z.Z.L._ P , z ", +" Y @.@.%.=.~.~.(.}.}.3.c.c.h.q.q.y.I.I.Q.Q.++++,+,+<+<+|+8+8+d+i+i+p+p+p+r+r+r+r+p+p+p+i+d+d+8+i+D+F+F+F+F+F+F+D+D+D+D+D+D+D+y+,+c.3.}.}.(.~.}.r+r+r+p+p+p+p+%.S N K K F z w w r o j a 6 6 3 3 ( { - R.A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+M R X.X.N.N.H.H.H.A.A.w.p.g.a.9.6.1././.{.!.>.>.>.H e A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+w+w+A+A+A+A+A+A+A+A+A+w+A+A+w+w+w+w+u+u+t+s+s+q+o+j+f+5+[+[+(+'+@+@+Z.V.^.@ o ", +" Y Y @.@.%.=.=.~.(.(.}.3.c.c.h.q.q.y.y.I.Q.Q.++++++,+,+<+|+|+8+8+d+i+i+i+p+p+p+p+p+p+i+i+i+d+i+D+F+F+F+F+F+F+F+D+D+D+D+D+D+y+<+q.c.3.3.}.(.(.~.h.r+r+r+p+p+p+p+Y N N K K F z w w o o j a 6 6 3 ( ( { - &.w+w+w+A+A+A+A+A+A+A+A+A+A+A+A+Z O X.N.N.H.H.A.A.w.w.n.g.a.9.6.1.<././.{.!.!.>.>.E n A+A+A+A+A+A+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+t+t+w+A+A+A+A+A+A+A+A+w+w+w+A+w+w+w+t+t+u+t+u+s+q+q+j+f+0+5+5+(+(+@+@+@+Z.V.B { ", +" Y Y @.@.%.%.=.~.(.(.}.3.3.c.h.q.q.q.y.I.I.Q.++++++,+,+<+<+|+|+8+8+d+d+i+i+i+i+i+i+i+i+d+i+y+G+G+F+F+F+F+F+F+F+D+D+D+D+y+|+y.q.h.c.3.3.}.(.(.~.I.r+r+r+p+p+p+8+S N N K F F z w r o o j a 6 6 3 ( { { - J w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+M.[ H.H.H.H.A.w.w.n.g.g.a.6.1.1.<./.{.{.!.!.>.>.$.[ [.w+w+w+w+w+w+t+t+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+A+A+w+w+w+w+w+w+w+t+t+w+A+w+w+w+t+t+u+t+u+s+s+q+j+j+0+0+5+(+(+'+@+@+Z.V.z._ K ", +" Y Y @.@.%.%.=.~.~.(.}.3.3.c.c.h.q.q.y.I.I.Q.Q.++++++,+,+<+<+|+|+8+8+d+d+d+i+i+i+i+d+d+r+F+G+G+F+F+F+F+F+F+F+D+D+D+D+i+I.q.q.h.c.c.3.3.}.(.~.~.++r+r+r+p+p+p+++S N N K F F z w r o o j a 6 3 3 ( { { - f w+w+w+t+u+u+u+u+u+u+u+u+u+u+u+u+| t A.H.A.w.w.n.g.g.a.2.1.1.<././.!.!.!.>.>.$.q { o+u+u+u+u+u+u+u+t+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+t+t+w+w+w+w+w+w+w+w+w+t+t+t+t+t+t+t+t+u+s+q+o+j+f+f+0+[+(+(+'+@+Z.V.P.^.@ ", +" Y Y Y @.@.%.=.~.~.(.}.}.3.c.c.h.q.q.y.y.I.Q.Q.++++++,+,+,+<+|+|+|+8+8+8+d+d+d+d+d+i+D+G+G+G+F+F+F+F+F+F+F+F+D+D+r+++y.y.q.q.h.c.c.3.}.}.(.~.~.|+r+r+r+p+p+p+y.S N N K F F z w r o j j a 6 3 3 ( { { - # q+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+n+e [ .w.w.n.g.a.a.6.1.1.<././.{.!.>.>.>.O / _ 1+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+w+w+w+w+w+w+t+t+t+u+u+t+t+t+t+t+t+u+q+q+q+o+j+f+0+5+5+(+'+@+@+Z.V.R.C ^ ", +" S Y Y @.@.%.=.=.~.(.(.}.3.3.c.h.h.q.q.y.I.I.Q.Q.++++++,+,+,+<+<+|+|+|+8+8+8+8+8+r+F+G+G+F+F+F+F+F+F+F+F+F+D+y+<+I.I.y.q.q.h.h.c.3.3.}.(.(.~.=.i+r+r+p+p+p+p+3.S N K K F z z w r o j a a 6 3 3 ( { - - $ (+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+i.# , E ./.a.2.1.1.<././.{.!. .O q , $ [.q+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+t+t+t+t+t+t+t+t+s+s+s+o+j+j+0+0+5+(+(+'+@+@+Z.V.L.{ K ", +" y.Y Y Y @.@.%.%.=.~.(.(.}.3.3.c.c.h.q.q.y.y.I.I.Q.Q.++++++,+,+,+<+<+|+|+|+|+|+i+D+G+G+F+F+F+F+F+F+F+F+F+F+D+8+Q.I.I.y.y.q.q.h.c.c.3.3.}.(.(.~.(.r+r+r+p+p+p+p+%.N N K K F z w w r o j a 6 6 3 3 ( { - $ $ u.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+s+`.Z - . $ / 7 7 p 7 7 / $ . - M M.q+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+u+u+t+t+t+t+t+u+s+q+q+o+j+f+0+0+[+(+(+@+@+@+V.R.^.@ ", +" ,+,+@.Y @.@.@.%.=.~.~.(.}.}.3.3.c.h.q.q.q.y.y.I.Q.Q.Q.++++++,+,+,+<+<+<+|+|+y+F+F+F+F+F+F+F+F+F+F+F+F+D+p+++Q.Q.I.y.y.q.q.q.h.c.3.3.}.}.(.~.~.h.r+r+r+p+p+p+i+S N N K F F z w w o o j a 6 6 3 ( ( { - $ $ 5.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+j+1+`.t.t.[.i.t.M.>+f+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+t+u+t+t+s+s+s+q+q+o+f+f+0+5+[+(+'+@+@+Z.V.P.B ^ ", +" |+p+|+%.Y @.@.%.=.=.~.(.(.}.3.3.c.c.h.q.q.y.y.I.I.Q.Q.++++++++,+,+,+,+<+i+F+F+F+F+F+F+F+F+F+F+F+F+F+r+,+++Q.Q.I.I.y.y.q.q.h.c.c.3.3.}.(.(.~.=.I.r+r+p+p+p+p+|+S N N K F F z w r o o j a 6 6 3 ( { { - $ $ ^.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+u+u+u+t+u+s+s+s+q+q+q+j+f+f+0+5+(+(+'+@+@+Z.V.L.{ N ", +" |+p+p+8+=.@.@.%.%.=.~.~.(.}.}.3.3.c.h.h.q.q.y.y.I.I.Q.Q.++++++++++,+<+y+F+F+F+F+F+F+F+F+F+F+F+F+D+|+++++Q.Q.I.I.y.y.q.q.h.h.c.3.3.}.}.(.~.~.=.,+r+r+p+p+p+p+Q.S N K K F z z w r o j j a 6 3 3 ( { { - $ $ &.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+u+u+u+t+s+s+q+q+o+j+f+0+5+[+(+(+@+@+@+V.R.).@ ", +" |+i+p+p+d+(.@.@.%.=.~.~.(.(.}.3.3.c.c.h.q.q.q.y.y.I.I.Q.Q.Q.++++++d+D+F+F+F+F+F+F+F+F+F+F+F+F+p+++++Q.Q.Q.I.I.y.y.q.q.q.h.c.c.3.3.}.(.(.~.=.=.8+r+r+p+p+p+p+c.N N K K F z w w r o j a a 6 3 3 ( { - - $ $ +.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+t+u+t+t+s+s+s+q+q+j+j+f+0+5+[+(+'+@+@+Z.V.P.B ^ ", +" |+i+p+p+p+i+3.@.%.%.=.~.~.(.}.}.3.3.c.c.h.q.q.q.y.y.I.I.Q.Q.Q.,+y+F+F+F+F+F+F+F+F+F+F+F+F+y+,+++++Q.Q.Q.I.I.y.y.q.q.q.h.c.c.3.3.}.}.(.~.~.=.=.p+r+p+p+p+p+p+=.N N K F F z w r o o j a 6 6 3 3 ( { - $ $ @ U u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+u+t+t+t+u+s+s+s+q+q+o+j+f+0+0+5+(+(+'+@+@+Z.V.z.{ S ", +" |+i+p+p+p+p+p+q.@.%.=.~.~.(.(.}.3.3.c.c.h.h.q.q.q.y.y.I.I.Q.|+D+D+D+D+F+F+F+F+F+F+F+F+D+8+++++Q.Q.Q.Q.I.I.y.y.q.q.q.h.h.c.c.3.3.}.(.(.~.=.=.3.r+r+p+p+p+p+i+S N K K F z z w r o o j a 6 3 3 ( { { - $ $ @ U u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+u+t+t+t+u+u+t+s+s+q+q+o+j+f+0+0+[+(+(+'+@+@+V.R.).@ ", +" |+i+i+p+p+p+p+p+++%.%.=.~.~.(.(.}.3.3.c.c.h.h.q.q.q.y.y.Q.p+D+D+D+D+D+D+D+D+D+D+D+D+p+++Q.Q.Q.Q.Q.I.I.I.y.y.q.q.q.h.h.c.c.3.3.}.(.(.~.~.=.%.I.r+p+p+p+p+p+<+N N K F F z w w r o j a a 6 3 3 ( { { - $ $ @ +.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+u+u+u+u+t+t+t+s+s+q+q+q+j+f+f+0+[+(+(+'+@+@+Z.R.P.s 3 ", +" |+i+i+p+p+p+p+p+r+,+=.=.=.~.(.(.}.}.3.3.c.c.h.h.q.q.q.,+y+D+D+D+D+D+D+D+D+D+D+D+y+,+Q.Q.Q.Q.Q.I.I.I.y.y.y.q.q.q.h.h.c.c.3.3.}.}.(.(.~.=.=.%.,+r+p+p+p+p+p+I.N K K F F z w r o o j a 6 6 3 3 ( { - - $ $ @ &.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+u+u+u+t+u+t+u+s+s+s+s+q+o+j+f+f+0+5+(+(+'+@+@+V.R.u.- S ", +" ,+i+i+i+p+p+p+p+p+r+8+(.=.~.~.(.(.}.3.3.3.c.c.h.h.y.i+D+D+D+D+D+D+D+D+D+D+D+D+8+Q.Q.Q.Q.I.I.I.I.I.y.y.y.q.q.q.h.h.c.c.3.3.3.}.(.(.~.~.=.%.%.d+p+p+p+p+p+i+3.N K K F z w w r o o j a 6 6 3 ( ( { - $ $ @ @ ^.u+u+u+u+u+u+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+u+u+u+t+t+u+t+t+t+s+s+q+q+j+j+f+0+5+5+(+'+'+@+V.R.R.).@ ", +" @.8+i+i+p+p+p+p+p+r+r+i+c.=.~.~.(.(.}.3.3.3.c.c.++y+y+D+D+D+D+D+D+D+D+D+D+r+++I.I.I.I.I.I.I.y.y.y.y.q.q.q.q.h.h.c.c.3.3.3.}.(.(.~.~.=.%.%.~.r+p+p+p+p+p+i+@.N K F F z w w r o j a a 6 3 3 ( { { - $ $ @ @ 5.t+t+t+t+t+t+u+u+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+u+u+t+t+u+t+u+u+u+u+t+t+t+u+s+s+s+s+q+o+j+f+0+0+5+(+(+'+@+Z.V.V.R.s ^ ", +" K Y |+i+i+p+p+p+p+p+r+r+r+I.~.~.~.(.(.}.3.3.c.|+y+y+y+y+D+D+D+D+D+D+D+D+|+I.y.y.y.y.y.y.y.y.y.y.q.q.q.q.q.h.h.c.c.3.3.3.}.(.(.~.~.=.=.%.%.h.p+p+p+p+p+i+8+N K K F z z w r o o j a 6 6 3 3 ( { - - $ $ @ . j.t+t+t+t+t+t+t+t+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+u+u+t+t+u+u+u+u+t+t+t+u+u+t+s+s+s+q+o+j+f+f+0+5+[+(+'+@+Z.V.R.u.- S ", +" K Y ,+i+p+p+p+p+p+p+r+r+r+,+~.~.~.(.(.}.q.p+y+y+y+y+y+y+y+D+D+D+D+r+Q.y.y.y.y.y.y.y.y.y.q.q.q.q.q.q.h.h.c.c.c.3.3.3.}.(.(.~.~.=.=.%.%.@.++p+p+p+p+p+i+Q.N K F F z w w r o j j a 6 3 3 ( ( { - $ $ @ @ . '+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+t+u+u+u+u+u+u+u+t+t+t+t+t+s+s+s+q+q+o+j+f+0+5+[+(+'+'+@+V.V.R.&.@ ", +" K K S Q.i+p+p+p+p+p+r+r+r+r+8+3.~.(.(.Q.r+y+y+y+y+y+y+y+y+y+y+y+|+q.q.q.q.q.q.q.q.q.q.q.q.q.q.q.h.h.h.c.c.3.3.3.3.}.(.(.(.~.~.=.%.%.@.@.8+p+p+p+p+i+i+3.K K F z z w r o o j a 6 6 3 3 ( { { - $ $ @ @ @ q+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+t+t+t+t+s+u+s+q+q+q+j+f+f+0+5+[+(+'+'+@+V.V.R.s 3 ", +" F K K N c.i+p+p+p+p+p+r+r+r+r+p+q.}.|+y+y+y+y+y+y+y+y+y+y+y+p+I.h.q.q.q.q.q.q.q.q.q.q.q.q.h.h.h.h.c.c.c.3.3.3.}.}.(.(.~.~.~.=.%.%.@.@.~.p+p+p+p+i+i+i+@.K F F z w w r o j j a 6 6 3 ( ( { - - $ $ @ . _ u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+t+u+t+s+q+q+q+o+j+f+0+0+[+(+(+'+@+@+V.R.u.$ ", +" F K K N N (.d+p+p+p+p+p+r+r+r+r+y+D+y+r+y+y+y+y+y+y+y+y+y+,+h.c.h.h.h.h.h.h.h.h.h.h.h.h.h.h.c.c.c.c.3.3.3.3.}.}.(.(.~.~.~.=.%.%.@.@.@.h.p+p+p+p+i+i+8+K K F z z w r o o j a 6 6 3 3 ( { { - $ $ @ @ . B u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+x+n+n+2+2+n+x+u+u+u+u+u+u+u+u+u+t+t+t+t+t+s+s+s+s+q+n+2+2+2+2+1+(+'+@+@+@+V.P.+.@ ", +" F F K K N N %.|+p+p+p+p+p+r+r+F+G+G+G+y+r+y+y+y+y+y+y+p+q.3.c.c.c.c.c.c.c.c.c.c.c.c.c.c.c.c.c.c.3.3.3.3.}.}.(.(.(.~.~.~.=.%.%.@.@.@.Y ++p+p+p+i+i+i+I.K F F z w w r o j j a 6 6 3 3 ( { - - $ $ @ . . +.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+2+Z { . + , / , . . _ ,.2+u+u+u+u+u+u+t+t+t+t+u+u+n+i.e . . - / , @ . $ I D.>+@+Z.R.P.s 3 ", +" z F F K K N N Y ++p+p+p+p+r+G+G+G+G+G+G+D+r+y+y+y+y+<+3.3.3.3.3.3.3.c.c.c.c.c.c.c.c.c.c.3.3.3.3.3.3.}.}.}.(.(.(.~.~.=.=.%.%.@.@.@.Y Y d+p+p+i+i+i+i+(.F F z w w r o o j a 6 6 3 3 ( { { - $ $ @ @ . . z.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+M.# , O 6..+7+7+3+}+!+w.!.E $ _ `.u+u+u+u+u+t+t+t+2+v $ y {.H.3+7+3+}+}+X.2.R 1 @ ,.>+V.R.j.$ ", +" z F K K N N N S h.p+p+y+G+G+G+G+G+G+G+G+F+y+r+r+y.(.}.}.}.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.}.}.}.(.(.(.~.~.~.=.=.%.%.@.@.@.Y Y (.p+p+i+i+i+i+i+S F z z w r r o j j a 6 6 3 3 ( { { - $ $ @ @ . @ j+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+v+e [ 1.b+h+b+a+a+7+3+}+_+!+!+$+T.V , v q+u+u+u+t+u+,.$ V _+h+b+b+a+7+3+}+}+!+!+-+.+<.7 $ B.V.P.+.+ ", +" z F F K K N N S S (.y+G+G+G+G+G+G+G+G+G+G+G+p+3.~.(.(.(.}.}.}.}.}.3.3.3.3.3.3.3.3.3.3.}.}.}.}.}.(.(.(.~.~.~.=.=.=.%.%.@.@.@.Y Y Y y.p+i+i+i+i+i+,+F F z w w r o o j a 6 6 3 3 ( { { - $ $ $ @ . . n u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+n+$ O a+h+h+l+h+b+a+a+3+}+_+!+-+$+.+X.w.q _ n+u+u+u+D 7 .+k+h+l+h+b+a+a+7+3+}+!+-+-+.+X.N.V @ i.R.R.f 6 ", +" w z F F K K N N @.<+p+F+G+G+G+G+G+G+G+G+G+F+r+,+(.~.~.(.(.(.(.(.}.}.}.}.}.}.}.}.}.}.}.}.(.(.(.(.(.~.~.~.~.=.=.%.%.%.@.@.@.Y Y Y S |+p+i+i+i+i+d+h.F z w w r o o j a a 6 3 3 3 ( { { - $ $ @ @ . . &.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+x+v+n+n+v+x+u+u+u+u+u+u+u+u+u+u+x+- .k+k+k+k+h+l+b+b+a+3+}+_+!+-+.+.+N.H.H.y e u+u+Z h 3+k+k+k+h+l+h+b+a+7+3+}+!+-+$+.+T.N.H.$.@ B.R.j.$ ", +" z z F F K K ~.8+i+p+p+D+G+G+G+G+G+G+G+y+r+r+r+i+h.~.~.~.~.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.~.~.~.~.~.=.=.=.%.%.@.@.@.@.Y Y Y S %.p+i+i+i+i+d+d+@.z z w r r o j j a 6 6 3 3 ( { { - $ $ $ @ . . . Z.w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+x+2+,._ . . . . . . { Z `.x+w+w+w+t+u+u+Q E k+k+k+k+k+k+h+h+b+a+7+3+_+!+-+.+X.N.H.H.w.[ [.2+, -+k+k+k+k+k+h+l+h+b+a+3+}+!+;+.+.+T.H.H.A.O { R.R.W $ K { . . . . . . $ o ~. ", +" w z z F K c.i+i+i+i+p+p+r+G+G+G+G+F+r+r+r+r+r+r+r+,+(.~.~.~.~.~.~.(.(.(.(.(.(.(.(.~.~.~.~.~.~.=.=.=.=.%.%.%.@.@.@.@.Y Y Y S S c.i+i+i+i+i+d+|+F z w w r o o j a a 6 3 3 ( ( { - - $ $ @ @ . . _ t+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+w+x+i.@ / R 1.N.!+3+}+-+H./.O 1 @ ,.s+w+t+t+v++ .+k+k+k+k+k+k+k+l+h+b+a+3+}+!+-+.+T.N.H.A.w. .$ v .h+k+k+k+l+k+k+h+h+b+a+7+}+_+!+.+X.T.H.A.A.n., i.V.R.8 F %.@ , O /.H.!+3+3+-+H./.R [ . r ", +" w w z N Q.d+i+i+i+i+i+i+p+p+D+G+D+p+p+r+r+r+r+r+r+r+i+q.=.=.~.~.~.~.~.~.~.~.~.~.~.~.~.~.=.=.=.=.%.%.%.%.@.@.@.@.Y Y Y Y S S S ++i+i+i+i+d+d+q.z w w r o o j a a 6 6 3 3 ( { { - $ $ $ @ . . . &.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+2+# p p.b+h+b+a+a+3+}+}+_+!+-+.+1.h $ M.u+t+D.y h+h+k+k+k+l+l+l+k+l+b+a+3+}+!+-+.+T.H.A.w.w.n.D + !+l+k+k+l+l+l+l+k+l+b+b+a+}+_+!+.+X.N.H.w.w.p.H v V.R.D.- [ a.a+b+b+a+a+3+3+}+_+!+;+$+a.t @ Y ", +" w Y <+d+d+d+i+i+i+i+i+i+p+8+q.d+p+p+p+p+r+r+r+r+r+r+r+<+(.=.=.=.=.=.=.=.=.=.=.=.=.=.=.=.%.%.%.%.%.@.@.@.@.@.Y Y Y Y S S S Y i+i+i+i+d+d+d+@.w w r o o j j a 6 6 3 3 ( { { - - $ $ @ @ . . . (+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+D.$ /.h+h+l+h+b+b+a+7+3+}+_+!+-+.+X.T.$.$ ,.u+,. .h+l+k+k+l+l+l+l+k+h+h+a+3+}+!+$+.+T.H.A.w.n.g.Z _ h+l+h+k+k+l+l+l+l+k+h+b+a+}+!+;+.+T.H.A.w.p.n. .@ R.D.@ .h+h+l+h+b+a+a+7+3+}+_+!+-+.+X.X./.- r ", +" =.8+d+d+d+d+d+i+i+i+i+i+++Y S @.++p+p+p+p+p+r+r+r+r+r+r+p+I.=.%.%.%.=.=.=.=.%.%.%.%.%.%.%.%.@.@.@.@.@.@.Y Y Y Y S S S S N c.i+i+i+d+d+d+|+F w r o o j j a 6 6 3 3 ( ( { { - $ $ @ @ . . . n u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+2+$ A.k+k+k+h+l+h+b+a+7+3+}+_+!+-+.+X.N.N.1.- B.v {.b+l+h+k+l+l+l+l+l+k+h+b+7+}+!+$+.+N.H.A.p.g.g.Z p b+h+h+k+k+l+l+l+l+k+l+b+a+3+!+-+.+T.H.A.w.g.g.>.. `.@ a.k+k+h+h+l+h+b+a+7+3+}+}+!+-+.+X.N.N.a., K ", +" y.8+8+d+d+d+d+d+d+i+i+h.S S S S S 3.d+p+p+p+p+p+p+r+r+r+r+r+d+c.%.%.%.%.%.%.%.%.%.%.@.@.@.@.@.@.@.@.Y Y Y Y Y S S S S N N ,+i+d+d+d+d+d+y.w r r o o j a a 6 6 3 3 ( { { - $ $ $ @ @ . . . j.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+x+# /.k+k+k+k+k+h+l+h+b+a+3+}+_+!+-+.+T.N.H.A.$.@ e /.b+h+l+k+l+l+l+l+l+k+l+b+7+}+!+$+X.H.H.w.n.a.a.Z q b+h+h+h+k+l+l+l+l+l+h+b+a+3+!+-+.+N.H.w.n.g.a.>.. | .k+k+k+k+k+k+l+h+b+a+3+}+_+!+-+.+X.N.H.H.{.+ ", +" 8+8+8+d+d+d+d+d+8+~.N N N S S S S @.++p+p+p+p+p+p+p+p+r+r+r+r+,+~.@.@.@.@.@.@.@.@.@.@.@.@.@.Y Y Y Y Y Y S S S S S N N Y i+d+d+d+d+d+d+%.r r o o j a a 6 6 3 3 ( { { - - $ $ @ @ . . . 1 u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+D.h h+k+k+k+k+k+k+h+l+b+a+7+}+_+!+$+.+T.H.H.A.w.p 3 /.b+h+h+h+k+l+l+l+l+k+h+b+3+}+!+.+X.H.A.w.g.a.6.Z q b+b+h+l+k+l+l+l+l+l+h+b+a+3+!+-+.+N.A.w.g.g.a.$.. 8 b+h+k+k+k+k+k+k+l+b+a+7+3+_+!+-+.+T.H.A.A.w.y j ", +" h.8+8+8+d+d+d+++S K K K N N N S S S S (.8+p+p+p+p+p+p+p+p+p+p+p+p+Q.%.@.@.@.@.@.@.@.@.Y Y Y Y Y Y Y S S S S S N N N K c.d+d+d+d+d+d+|+z r o o j a a 6 6 3 3 ( ( { { - $ $ $ @ . . . . b.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+| /.h+k+k+k+l+l+l+k+h+h+a+7+3+_+!+.+X.N.H.A.w.p. .^ /.b+b+h+l+k+l+l+l+l+k+l+b+3+}+!+.+T.H.A.p.g.a.6.Z q a+b+b+h+h+l+l+l+l+l+l+b+a+3+!+$+X.N.A.p.g.g.9.$.. -.l+h+k+l+l+l+l+k+k+h+b+7+3+}+!+$+.+N.H.A.w.p.!.. ", +" |+8+8+8+d+q.F F F K K K N N N N S S S Y y.p+p+p+p+p+p+p+p+p+p+p+p+i+q.@.Y Y Y Y Y Y Y Y Y Y S S S S S S N N N N K K <+d+d+d+d+d+8+h.r o o j a a 6 6 3 3 ( ( { { - $ $ $ @ @ . . . _ s+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+. T.l+h+k+k+l+l+l+l+k+l+b+a+3+_+!+.+X.N.H.A.p.n.6.^ /.a+b+b+l+k+k+l+l+l+k+l+a+3+}+!+.+N.H.A.n.a.6.1.Z q a+a+b+h+h+k+l+l+l+k+l+b+a+}+!+.+X.N.A.n.a.a.2.$.. s.h+l+k+k+l+l+l+l+k+l+b+a+3+_+!+$+.+N.H.A.p.n.g.$ ", +" (.8+8+8+(.w z z F F F K K K N N N N S S S =.<+p+p+p+p+p+p+p+p+p+p+p+p+|+3.Y Y Y Y Y Y S S S S S S S N N N N K K K %.d+d+d+d+8+8+8+Y o o j j a 6 6 3 3 3 ( { { - - $ $ @ @ . . . . 5.u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+v+. }+h+l+h+k+l+l+l+l+l+h+h+a+3+}+!+.+X.H.A.w.g.g.a.^ /.a+b+b+h+l+k+l+l+k+h+h+a+3+}+;+.+N.H.w.g.a.6.1.Z q a+a+b+b+h+k+k+l+l+k+h+b+a+}+!+.+T.N.w.g.a.9.6.$.. K.h+l+h+k+l+l+l+l+k+l+h+a+3+_+;+$+.+H.A.w.g.g.a./ ", +" ,+,+S r w w z z z F F K K K K N N N N S S S 3.8+p+p+p+p+p+p+p+p+p+p+p+p+,+}.S S S S S S S S S N N N N K K K K F y.d+d+d+8+8+8+,+o o j a a 6 6 3 3 3 ( { { - - $ $ @ @ . . . . f u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+n+. b+h+h+l+k+l+l+l+l+l+k+h+a+3+}+;+.+T.H.A.p.g.g.a.^ /.7+a+b+b+h+h+k+k+k+l+h+a+3+_+-+X.N.A.w.g.a.6.<.U q 7+a+a+b+b+l+h+k+k+h+h+a+7+}+!+.+T.H.w.g.a.6.2. .. T.b+h+l+k+l+l+l+l+l+h+h+a+3+_+;+$+X.H.A.p.g.g.a.[ ", +" z o o r w w w z z F F F K K K K N N N N N S Y y.i+p+p+p+p+p+p+p+p+p+p+p+p+,+~.S S S S N N N N N K K K K F F N 8+d+d+8+8+8+8+}.o j a a 6 6 3 3 3 ( { { - - $ $ $ @ @ . . . @ @+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+n+. a+b+h+l+k+l+l+l+l+l+k+b+a+3+_+-+.+N.H.w.n.g.a.2.^ /.3+a+a+b+h+l+h+k+h+h+b+a+}+_+-+X.H.A.w.g.a.2./.U q 3+7+a+b+b+l+h+k+k+l+b+a+7+}+!+.+N.H.w.g.a.1.<. .. T.b+h+l+k+l+l+l+l+l+k+h+a+3+_+!+.+T.H.w.n.g.a.6.[ ", +" j j o o r r w w z z z F F F K K K K N N N N N N %.++i+i+i+i+i+i+i+i+i+i+i+i+i+++%.N N N N N K K K K F F F z }.d+8+8+8+8+8+8+K j a a 6 6 3 3 3 ( ( { { - $ $ $ @ @ . . . . W u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+n+. a+b+b+h+h+k+l+l+l+l+k+b+a+3+_+-+X.N.H.w.g.a.9.1.^ /.3+7+a+b+b+h+l+h+l+h+b+7+}+!+$+T.H.A.p.g.2.<./.U q 3+7+a+a+b+h+h+h+l+h+b+a+3+_+-+X.N.H.p.g.2.1.<. .. T.a+b+h+h+k+l+l+l+l+k+b+a+3+_+-+.+N.H.w.g.a.9.1.[ ", +" j j o o r r w w w z z z F F F F K K K K N N N N N ~.<+i+i+i+i+i+i+i+i+i+i+i+i+i+++~.K K K K F F F F z z z ,+8+8+8+8+8+8+I.j a a 6 6 3 3 3 ( ( { { - $ $ $ @ @ . . . . _ f+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+u+n+. 7+a+b+b+l+k+l+l+l+l+h+b+a+3+_+-+X.H.A.w.g.a.6.1.^ {.3+7+a+a+b+b+h+h+h+b+a+3+}+!+.+N.H.w.n.a.6.<./.U q 3+3+7+a+a+b+h+h+h+b+a+7+}+_+-+X.N.A.n.a.6.1.<. .. K.a+b+b+h+k+k+l+l+l+h+b+a+3+_+-+.+N.A.w.g.a.6.1.[ ", +" j j o o o r r w w w z z z F F F F K K K K K K K N N }.|+i+i+i+i+i+i+i+i+i+i+i+i+d+++=.K F F F z z z w Y 8+8+8+8+8+8+8+Y a a 6 6 3 3 3 ( ( { { - $ $ $ @ @ . . . . @ L.t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+n+. 3+a+a+b+h+l+k+k+k+k+h+a+7+}+!+$+T.H.A.p.g.9.2.<.^ {.3+7+a+a+b+b+h+h+h+b+a+3+}+!+.+N.H.w.n.a.6.<./.U q }+3+7+a+a+b+h+h+h+b+a+7+3+_+-+X.N.A.n.a.6.1.<. .. K.a+a+b+h+l+h+k+k+k+l+b+7+3+!+.+X.N.w.n.g.9.2.<.[ ", +" a a j j o o o r r w w w z z z F F F F F F K K K K K K N h.d+i+i+i+i+i+d+d+d+d+d+d+d+d+++=.F z z w w w I.8+8+8+8+8+8+,+j 6 6 6 3 3 3 ( ( { { - $ $ $ @ @ . . . . . &.t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+n+. 3+7+a+a+h+h+h+k+k+h+h+a+7+}+!+.+N.H.A.n.a.6.1./.^ {.}+3+7+a+a+b+b+b+b+a+a+3+_+!+.+N.H.w.g.a.1.<./.U q }+3+3+7+a+b+b+b+b+b+a+3+}+!+$+T.H.A.g.a.1.<.<. .. K.7+7+a+h+h+l+k+k+h+h+a+7+}+!+.+X.H.w.n.g.6.1././ ", +" j a j j o o o r r r w w w w z z z z F F F F F F F F F F N c.8+d+d+d+d+d+d+d+d+d+d+d+d+d+<+3.F w r N 8+8+8+8+8+8+|+(.6 6 6 3 3 3 ( { { { - $ $ $ @ @ . . . . . s t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+t+n+. 3+7+a+a+b+h+h+h+l+l+b+a+3+}+!+.+N.H.w.g.a.2.<./.^ !.}+3+3+7+a+a+b+b+a+a+7+}+!+;+.+N.A.w.g.9.2././.U q }+}+3+3+a+a+a+b+b+a+7+3+}+!+.+N.H.w.g.9.2.<./. .. K.3+7+a+b+h+h+l+l+l+b+a+3+}+!+.+T.H.w.g.a.2.1././ ", +" - a a a j j o o o o r r w w w w w z z z z z F F F F F F F F S q.8+d+d+d+d+d+d+d+d+d+d+8+8+8+|+q.N h.8+8+8+8+|+|+|+w 6 3 3 3 ( ( { { { - $ $ $ @ @ . . . . . _ 5+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+s+n+. }+3+7+a+a+b+b+h+h+b+a+3+}+_+-+.+N.A.w.g.a.1.<./.^ !.}+3+3+3+7+a+a+a+a+7+3+}+!+-+X.H.A.p.g.6.1././.U q }+}+3+3+7+a+a+a+a+a+3+}+_+!+.+N.H.w.g.6.1.<./. .. K.3+3+a+a+b+b+h+b+b+a+3+}+_+!+.+N.H.w.g.a.1.<././ ", +" F s R.a a a j j j o o o o r r r w w w w w w z z z z z z z z z z z S q.d+d+d+d+d+8+8+8+8+8+8+8+8+8+d+D+p+8+8+|+|+|+q.6 3 3 3 ( ( { { - - $ $ $ @ @ . . . . . - @+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+n+. }+3+3+7+a+b+b+b+b+a+a+3+}+!+-+X.N.A.p.g.6.<.<./.^ !.}+}+}+3+3+7+7+a+7+3+3+_+!+$+X.H.w.n.a.6.<././.U q }+}+}+}+3+7+7+a+7+7+3+}+_+-+.+H.A.w.a.6.<.<./. .. K.3+3+7+a+a+a+b+b+a+a+3+}+!+-+X.N.H.w.g.6.<.<././ ", +" $ 5+C+B.6 6 a a j j j o o o o o r r r w w w w w w w w w w w w w w w w N c.8+8+8+8+8+8+8+8+8+8+8+8+y+G+G+F+y+8+|+|+K 3 3 3 ( ( { { - - $ $ $ @ @ . . . . . $ L.q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+n+. }+3+3+3+7+a+a+a+a+a+7+}+_+!+.+X.H.A.g.a.6.<.<.{.^ !.}+}+}+3+3+7+3+3+3+3+}+!+-+.+N.A.w.n.a.6.1./.{.U q _+}+}+}+3+7+3+3+3+3+}+_+!+$+X.H.A.w.a.6.1.<./.V . K.}+3+3+7+a+a+b+a+a+7+}+_+!+-+X.H.A.n.a.6.<.<.{./ ", +" - ^.C+C+E+5.6 6 6 a a j j j j o o o o o r r r r r w w w w w w w w w w w w r K c.|+8+8+8+8+8+8+8+8+8+F+G+G+F+F+F+y+,+3 3 ( ( { { { - - $ $ $ @ @ . . . . . + j.q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+q+n+. _+}+}+3+3+7+7+a+7+7+3+}+_+!+.+T.H.w.g.9.6.<./.!.^ !._+}+}+}+3+3+3+3+}+}+_+!+$+.+N.A.w.n.a.6.<./.!.U q _+}+}+}+}+3+3+3+}+}+_+!+!+.+T.H.w.w.a.6.<././. .. K.}+}+3+3+7+7+7+7+7+3+}+_+!+.+T.H.A.g.9.6.<./.!./ ", +" S f A+C+C+E+E+|.6 6 6 6 a a a j j j j o o o o o o o o r r r r r r r r r r o o o o z 3.<+8+8+8+8+8+|+y+G+F+F+F+F+F+F+i+++=.o { { { - - $ $ $ @ @ . . . . . $ b.j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+j+2+. _+}+}+}+3+3+3+3+3+3+}+_+!+-+.+N.w.p.g.9.6.<./.!.^ !._+}+}+}+}+}+}+}+}+_+!+;+.+X.N.A.w.g.9.6.<./.{.U q _+_+}+}+}+}+}+3+}+}+!+!+-+.+N.H.A.w.a.1.<././. .. K._+}+}+3+3+3+3+3+3+}+_+!+-+.+H.A.w.g.6.1.<.{.!./ ", +" @ (+C+C+C+C+C+C+t.6 6 6 6 6 a a a a j j j j j o o o o o o o o o o o o o o o o o o j j r =.,+|+|+|+8+F+F+F+F+F+F+F+D+|+<+<+<+y.S 3 $ $ $ $ @ @ . . . . . $ j.f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+f+2+. !+_+_+}+}+}+}+}+}+}+_+!+-+.+T.H.w.p.g.6.1././.!.^ !.!+_+_+_+}+}+}+}+_+!+!+-+.+T.H.A.n.g.9.1.<.{.!.U q !+_+_+_+}+_+}+}+_+!+!+-+.+X.N.A.w.n.9.1.<././. .. K._+_+}+}+}+}+}+}+}+_+!+-+.+T.H.A.w.g.6.1./.{.!./ ", +" { &.B+C+C+C+C+C+B+B+R.6 3 6 6 6 6 6 a a a a a j j j j j j j j j j j j j j j j j j j j a a a a S I.|+y+F+F+F+F+F+F+F+p+<+<+<+<+<+,+,+h.S { @ @ . . . . . { L.0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+2+. !+!+!+!+_+_+_+_+_+!+!+$+.+X.H.A.w.g.a.6.<./.{.>.^ !.!+!+!+!+!+_+_+!+!+!+-+.+X.N.H.w.g.a.2.1.<.{.!.U p !+!+!+!+_+_+_+!+!+!+-+$+.+T.H.A.p.g.2.1.<./.!. .. K.!+!+!+_+_+_+_+_+!+!+$+.+X.N.A.w.p.a.6.<././.!./ ", +" Y 8 A+B+B+B+B+B+B+B+A+A+R.e 3 3 3 6 6 6 6 6 a a a a a a a a j j j j j j j j a a a a a a a a 6 6 6 6 S y+F+F+F+F+F+F+F+|+<+<+<+<+,+,+,+,+,+++}.F $ . . . 8 V.5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+2+. !+!+!+!+!+!+!+!+!+;+-+.+X.N.H.A.n.g.a.1.<./.{.>.^ >.!+!+!+!+!+!+!+!+;+-+.+X.N.H.A.p.g.a.1.1./.{.!.U p !+!+!+!+!+!+!+!+;+-+.+.+T.H.H.w.n.a.1.1./.{.!. .. x.!+!+!+!+!+!+!+!+;+-+.+X.N.H.A.p.g.a.1.<././.!./ ", +" @ Z.B+A+A+A+A+A+A+A+w+t+s+0+J 3 3 3 3 3 6 6 6 6 6 6 6 6 a a a a a a a a a a a a 6 6 6 6 6 6 6 6 3 3 }.|+8+r+F+F+F+F+p+<+<+<+,+,+,+,+,+,+,+,+,+,+++3.K M '+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+(+>+. -+!+!+!+;+;+!+-+$+.+.+T.N.H.A.w.g.a.2.1.<./.{.>.{ >.;+;+;+!+!+;+-+-+.+.+X.N.H.H.w.g.g.9.1.<./.!.!.U p ;+!+!+;+-+-+-+$+.+.+X.T.N.H.A.p.g.9.1.<./.!.!. .. A.!+!+!+!+!+!+-+$+.+.+T.N.H.A.w.g.a.2.<././.{.>./ ", +" 6 W A+A+A+A+A+A+w+t+t+s+q+o+j+f+&.^ 3 3 3 3 3 3 3 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 3 3 3 3 3 r <+<+<+<+<+i+D+D+<+<+,+,+,+,+,+,+,+,+,+,+,+,+,+++D.'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+'+`.. -+-+-+$+$+$+.+.+.+X.N.H.H.A.w.n.a.2.2.1./.!.{.>.{ >.-+-+-+-+.+.+.+.+X.T.N.N.A.A.n.a.a.6.2.<./.{.!.U p -+-+$+.+.+$+$+.+.+T.N.N.H.A.w.g.a.2.2.<./.{.!. .. x.-+-+-+$+$+.+.+.+X.N.H.H.A.w.n.g.a.1.<./.{.!.>./ ", +" =.( q+A+A+A+w+t+t+s+q+q+j+j+f+0+0+[+f.e ( ( 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 ( h.<+<+<+<+<+<+<+@.@.++,+,+,+,+,+,+,+,+,+,+,+,+y.L.@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+@+`.. .+$+.+.+.+.+.+X.T.N.H.H.A.w.n.a.9.1.<.<./.!.!.>.{ $.$+$+.+.+.+.+X.X.N.H.H.A.w.w.g.a.6.1.<./.{.!.!.U p -+.+.+.+.+.+X.X.N.N.H.H.A.w.g.a.9.6.<./.{.!.!. .. x..+.+.+.+.+.+X.T.N.H.H.A.w.n.a.a.6.1././.{.!.>./ ", +" @ z.A+t+u+t+u+s+q+o+j+f+0+0+[+(+(+'+'+@+&.3 ( ( ( ( ( 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 ( ( ( ( ( K <+<+<+<+<+<+<+Q.- $ - z (.,+,+,+,+,+,+,+,+++i.R.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.M.. .+.+.+.+X.T.N.N.H.H.A.w.w.p.g.9.2.2.<././.!.!.>.{ $..+.+.+.+X.T.N.H.H.A.A.w.n.g.a.2.1.1.<./.{.!.>.U p .+.+.+X.X.T.N.N.H.H.A.w.p.g.g.6.6.1././.!.!.!.V . x..+.+.+X.X.T.N.H.H.A.w.w.p.g.9.2.1.<././.!.!.>./ ", +" j J t+t+t+s+s+q+o+j+f+0+5+5+(+(+'+@+@+Z.V.V.j.v { { ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( { { ( I.<+<+<+<+<+,+,+K $ $ $ @ @ ( S y.,+,+,+++i.B.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.M.. .+.+X.T.T.N.H.A.A.A.w.p.n.g.a.2.1.<././.{.!.!.>.{ $.X.X.T.N.N.H.H.A.A.w.w.g.g.a.2.1.<././.{.!.!.>.M p .+X.X.N.N.N.H.A.A.w.w.p.g.g.a.1.2.<./.{.!.!.!.V . k..+X.T.N.N.H.H.A.A.w.p.n.g.a.2.1.1././.{.>.!.>./ ", +" - 0+s+s+q+o+j+f+f+0+5+[+(+'+@+@+Z.V.R.R.L.L.L.z.^.D { { { { { { { { ( ( ( ( ( ( { { { { { { { { { { Y <+<+<+,+,+,+,+q.$ $ @ @ @ . . . . 3 Y 3.f.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.B.. T.X.T.N.H.H.A.A.w.w.n.g.g.9.6.1.<././.{.!.!.!.!.{ $.X.T.N.H.H.H.A.w.w.n.g.a.9.2.2.<./././.{.!.!.!.M p .+T.N.N.H.H.A.w.p.p.g.g.a.a.2.<.<.<./.!.!.!.!. .. s.X.N.N.H.H.A.w.w.w.n.g.g.9.6.1.<.<./.{.!.>.!.>./ ", +" $ 5.s+q+q+j+f+0+0+[+(+(+'+@+@+V.P.L.L.L.z.z.u.u.u.u.u.|.C { { { { { { { { { { { { { { { { { { - - - 3 ++,+,+,+,+,+,+,+o @ @ @ . . . . . @ C |.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.t.. N.N.H.H.A.A.w.p.n.g.g.a.6.6.1.<././.{.!.!.!.!.!.{ $.T.N.H.H.H.A.w.n.g.a.a.6.1.2.<././././.{.!.!.!.M h T.N.H.H.H.A.w.w.n.g.a.9.2.1.<./././.!.!.!.!.!. .. s.T.N.H.H.A.w.w.n.g.g.a.6.6.1.<././.{.!.!.>.!.!./ ", +" F s q+q+o+j+f+0+5+[+(+'+@+@+Z.V.R.L.L.z.u.u.u.j.j.j.j.j.j.j.|.M | - - - - - - - - - - - - - - - - $ $ }.,+,+,+,+,+,+,+(.@ @ . . . . . | M 5.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.t.. N.N.H.A.w.w.p.g.g.a.2.1.1.<.<././.!.!.!.!.!.!.!.{ .N.H.H.A.w.p.g.g.a.a.9.6.2.1./././.{.{.!.!.!.!.M h N.N.H.A.w.w.n.g.g.a.6.6.1.1././././.!.!.!.!.{. .. s.N.H.H.A.w.w.n.g.a.2.1.1.<.<./././.!.!.!.!.!.!./ ", +" $ '+q+j+f+0+0+5+(+'+'+@+Z.R.R.L.z.z.u.j.j.j.j.j.j.j.j.b.b.b.b.b.b.&.C _ $ $ $ $ $ $ $ $ $ $ $ $ $ $ w ,+,+,+,+,+,+,+++( . . . @ _ B ^.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.b.i.. N.H.A.w.w.p.g.g.a.9.6.1.<.<././.!.!.!.!.!.!.!.!.{ .H.H.A.w.p.g.g.a.9.2.6.2.<.<./.{.{.!.!.!.!.!.!.M h N.H.A.w.w.n.g.g.9.6.6.1.<.<./.{././.!.!.!.!.{. .. s.H.A.w.w.p.n.g.a.9.6.1.<.<././.{.{.!.!.!.!.!.!./ ", +" - C z.u.u.j.j.b.5.5.5.|.^.^.^.).&.&.&.&.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.U B 8 - $ $ $ $ $ $ $ $ $ { Q.,+,+,+,+,+,+,+Y . - 8 C U +.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.,.. {.!.>.>.$.$. . . . .V V V V V R R R R R R O R R { O !.>.>.$.$. . . . . .V V V V R R R R R R R R R I [ {.!.>.>.$.$. . . . .V V V V V R R R O O R R R H . -.!.>.>.$.$. . . . .V V V V V R R R R O R R R R , S ", +"N . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @ @ @ @ @ @ @ @ @ %.++++Q.q.3.~.@.z @ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . j . . . . . . . . . . . . . . . . . . . . . . . . . { . . . . . . . . . . . . . . . . . . . . . . . . r . . . . . . . . . . . . . . . . . . . . . . . . . . - . . . . . . . . . . . . . . . . . . . . . . . . K " +}; diff --git a/include/constants.hpp b/include/constants.hpp new file mode 100644 index 0000000..35bf12a --- /dev/null +++ b/include/constants.hpp @@ -0,0 +1,90 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _CONSTANTS_H_ +#define _CONSTANTS_H_ + +#ifndef EMSPHINX_GIT_BRANCH + static_assert(false, "git branch isn't defined (please -DEMSPHINX_GIT_BRANCH=___ or redefine empshinx::Version"); +#endif + +#ifndef EMSPHINX_GIT_HASH + static_assert(false, "git hash isn't defined (please -DEMSPHINX_GIT_HASH=___ or redefine empshinx::Version"); +#endif + +#define EM_STRINGIFY(x) #x +#define EM_2_STR(x) EM_STRINGIFY(x) + +#include + +namespace emsphinx { + //@remarks + //crystallographic orientations in this code are described as passive rotations from the sample to the crystal frame + //this is the most common choice for EBSD codes and crystallography codes in the US and consistent with EMsoft + //if your use case requires another choice you'll need to adjust the output accordingly + + //axis angle convention switch + //euler angles are always treated in the passive sense such that + //eu[3] = {alpha, beta, gamma} is a rotation of the reference frame by: + // -first alpha about the z axis + // -next beta about the y' axis + // -finally gamma bout the z'' axis + //pijk (+/-1) is \hat{i} * \hat{j} * \hat{k} (the complex components of the quaternion) + //for pijk = +1 a passive rotation of 120@[1,1,1] -> {0.5, +0.5 * \hat{i}, +0.5 * \hat{j}, +0.5 * \hat{k}} + //for pijk = -1 a passive rotation of 120@[1,1,1] -> {0.5, -0.5 * \hat{i}, -0.5 * \hat{j}, -0.5 * \hat{k}} + const int pijk = +1; + static_assert(pijk == +1 || pijk == -1, "pijk must be +/-1"); + + //@brief: helper class to hold commonly used floating point constants + template + struct Constants { + static_assert(std::is_floating_point::value, "Real must be a floating point type");//disallow integers + static const Real pi ;//pi + static const Real pi2 ;//pi*2 + static const Real pi_2;//pi/2 + }; + + static const std::string GitBranch = EM_2_STR(EMSPHINX_GIT_BRANCH); + static const std::string GitHash = EM_2_STR(EMSPHINX_GIT_HASH); + static const std::string Version = GitBranch + ':' + GitHash; +} + +namespace emsphinx { + //constants are given with 72 decimal places which is enough for IEEE octuple precision (if you ever need to support 512 bit fp numbers you'll need to recompute these...) + template const Real Constants::pi = Real(3.14159265358979323846264338327950288419716939937510582097494459230781641); + template const Real Constants::pi2 = Real(6.28318530717958647692528676655900576839433879875021164194988918461563281); + template const Real Constants::pi_2 = Real(1.57079632679489661923132169163975144209858469968755291048747229615390820 ); +} + +#endif//_CONSTANTS_H_ diff --git a/include/idx/base.hpp b/include/idx/base.hpp new file mode 100644 index 0000000..c92cbb0 --- /dev/null +++ b/include/idx/base.hpp @@ -0,0 +1,164 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _IDX_BASE_H_ +#define _IDX_BASE_H_ + +#include +#include +#include +#include + +namespace emsphinx { + + //@brief: abstract base class to provide images to index + class ImageSource { + + public: + //possible pixel types + enum class Bits { + UNK = 0 ,//unknown + U8 = 8 ,//uint8_t + U16 = 16,//uint16_t + F32 = 32 //float + }; + + //@brief : get the bytes per pixel + //@return: bytes per pixel + size_t pixBytes() const; + + //@brief : get the bytes per image + //@return: bit depth of pixels + size_t imBytes() const {return pixBytes() * numPix();} + + //@brief : get the pixel type of images + //@return: bit depth of pixels + Bits pixelType() const {return bits;} + + //@brief : get the width of images + //@return: width of images in pixels + size_t width() const {return w;} + + //@brief : get the width of images + //@return: height of images in pixels + size_t height() const {return h;} + + //@brief : get pixels per image + //@return: pixels per image + size_t numPix() const {return width() * height();} + + //@brief : extract the next batch of images into a buffer + //@param out: location to write extracted images + //@param cnt: maximum number of images to extract + //@return : vector of the indices of each image extracted (e.g. {0,2,1,3} for the first 4 images but out of order) + //@note : implementations should be thread safe (only for other extract calls) + virtual std::vector extract(char * const out, const size_t cnt) const = 0; + + protected: + Bits bits;//pixel type + size_t w ;//width of image + size_t h ;//height of image + }; + + //@brief: abstract base class to preprocess images + template + class ImageProcessor { + static_assert(std::is_floating_point::value, "Real must be a floating point type"); + + public: + + //@brief : process an image out of place (e.g. do background subtraction) + //@param im : image to process + //@param buf: location to write floating point processed image + virtual void process(uint8_t const * const im, Real * const buf) = 0; + virtual void process(uint16_t const * const im, Real * const buf) = 0; + virtual void process(float const * const im, Real * const buf) = 0; + + //@brief : get size of target image to process + //@return: size of input image in pixels + virtual size_t numPix() const = 0; + + //@brief : get a copy of the stored image processor + //@return: unique pointer to copy of current processor + virtual std::unique_ptr clone() const = 0; + + //@brief: default destructor (for unique_ptr) + virtual ~ImageProcessor() = default; + }; + + //@brief: abstract bass class to encapsulate back projection of images to the sphere + template + class BackProjector { + public: + //@brief : unproject an image from the detector to a square legendre grid + //@param im : processed image to back project to unit sphere + //@param sph: location to write back projected image + //@param iq : location to write image image quality (NULL to skip computation) + //@return : sqrt(integral of im^2) (properly weighted on the sphere) + virtual Real unproject(Real * const im, Real * const sph, Real * const iq = NULL) = 0; + + //@brief: compute quaternion to rotate back projection such that is centered around the north pole + //@return: quaternion as wxyz + virtual std::array northPoleQuat() const {return std::array({1, 0, 0, 0});} + + //@brief : get a copy of the stored back projector + //@return: unique pointer to copy of current projector + virtual std::unique_ptr clone() const = 0; + + //@brief: default destructor (for unique_ptr) + virtual ~BackProjector() = default; + }; +} + +//////////////////////////////////////////////////////////////////////// +// Implementations // +//////////////////////////////////////////////////////////////////////// + +namespace emsphinx { + + //@brief : get the bytes per pixel + //@return: bytes per pixel + size_t ImageSource::pixBytes() const { + switch(pixelType()) { + case Bits::UNK: return 0; + case Bits::U8 : return 1; + case Bits::U16: return 2; + case Bits::F32: return 4; + } + return 0; + } +} + +#endif//_IDX_BASE_H_ + diff --git a/include/idx/indexer.hpp b/include/idx/indexer.hpp new file mode 100644 index 0000000..3ae6520 --- /dev/null +++ b/include/idx/indexer.hpp @@ -0,0 +1,350 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _SPHERE_INDEXER_H_ +#define _SPHERE_INDEXER_H_ + +#include +#include +#include +#include + +#include "idx/base.hpp" +#include "idx/master.hpp" +#include "sht/square_sht.hpp" +#include "sht/sht_xcorr.hpp" +#include "xtal/quaternion.hpp" + + +namespace emsphinx { + + //@brief: a single indexing result + template + struct Result { + Real corr ;//maximum cross correlation + Real iq ;//image quality metric + int phase;//phase of maximum cross correlation + Real qu[4];//orientation of maximum cross correlation + + //@brief : comparison operator + //@param rhs: result to compare against + //@return : this < rhs (such that sort is in descending corr) + bool operator<(const Result& rhs) const {return corr > rhs.corr;}//sort from largest to smallest cross correlation + }; + + //@brief: abstract indexer base class + template + struct Indexer { + //storage values values + const size_t mBw ;//bandwidth to use for indexing + const size_t dim ;//side length of square legendre grid for spherical harmonic transformer + const std::array quNp;//quaternion to rotate detector such that is centered around the north pole (correction in orientation is then required) + Real sum2;//sumsq of the back projected image in sph (integral over the window of sph^2 * dOmega) + std::vector > > pSym;//psuedosymmetric orientations to check for each phase + + //larger work spaces + std::vector wrk ;//space to hold processed image + std::vector sph ;//space to hold image back projected onto sphere + std::vector< std::complex > gln ;//space to hold SHT of image begin indexed + + //calculators + std::unique_ptr< ImageProcessor > prc ;//image processor + std::unique_ptr< BackProjector > prj ;//back projector + square::DiscreteSHT sht ;//spherical harmonic transform calculator + std::vector< std::unique_ptr< sphere::PhaseCorrelator > > xc ;//spherical correlator (for each phase) + + //@brief : construct an indexer + //@param bw : bandwidth + //@param imPrc : image processor + //@param backPrj: back projector + //@param corr : cross correlator + Indexer(const size_t bw, std::unique_ptr< ImageProcessor > imPrc, std::unique_ptr< BackProjector > backPrj, const std::vector< std::unique_ptr< sphere::PhaseCorrelator > >& corrs); + + //@brief : estimate a reasonable batch size + //@param bw: bandwidth + //@param nt: number of worker threads + //@param np: number of images to index + //@return : a reasonable batch size (a number of images that should take on the order of 1s to index + static size_t BatchEstimate(const size_t bw, const size_t nt, const size_t np); + + //@brief : index a single image + //@param pat: pointer to image to index in row major order + //@param n : number of results to keep (top n), user is responsible for making sure this is reasonable (extra points will be filled with an invalid phase) + //@param res: location to write results + //@param ref: true/false to use newton's method refinement/triquadratic interpolation + //@template Pix: pixel type of image (will be cast to Real) + //@note : multiple results can be written (currently there is an additional result per psuedosymmetric orientation to check) + template + void indexImage(Pix* pat, Result*const res, const size_t n, const bool ref); + + //@brief : refine the orientation of a single image + //@param pat: pointer to image to refine in row major order + //@param res: location to write result (and read initial phase + orientation) + //@template Pix: pixel type of image (will be cast to Real) + template + void refineImage(Pix* pat, Result& res); + + //@brief : get a copy of the stored image processor + //@return: unique pointer to copy of current processor + std::unique_ptr clone() const {return std::unique_ptr(new Indexer(mBw, std::move(prc->clone()), std::move(prj->clone()), xc));} + + protected: + + //@brief : process an image, unproject to the sphere, and compute spherical harmonic coefficients + //@param im: image to process + //@return : image quality + template + Real computeHarmonics(Pix* im); + + //@brief : index the spherical function currently held in sph + //@param p : phase to correlate with + //@param gln: spectra of image to index + //@param ref: true/false to use newton's method refinement/triquadratic interpolation + //@return : correlation result (with ZYZ euler angle stored in qu[0,1,2]) + Result correlate(const size_t p, std::complex const * gln, const bool ref); + + //@brief : refine the spherical function currently held in sph + //@param p : phase to refine with + //@param gln: spectra of image to refine + //@param eu : initial ZYZ euler angle to refine from + //@return : refinement result (with ZYZ euler angle stored in qu[0,1,2]) + Result refine(const size_t p, std::complex const * gln, Real const * const eu); + }; + + +} + +//////////////////////////////////////////////////////////////////////// +// Implementations // +//////////////////////////////////////////////////////////////////////// + +namespace emsphinx { + + //////////////////////////////////////////////////////////////////////// + // Indexer // + //////////////////////////////////////////////////////////////////////// + + //@brief : construct an indexer + //@param bw : bandwidth + //@param imPrc : image processor + //@param backPrj: back projector + //@param corr : cross correlator + template + Indexer::Indexer(const size_t bw, std::unique_ptr< ImageProcessor > imPrc, std::unique_ptr< BackProjector > backPrj, const std::vector< std::unique_ptr< sphere::PhaseCorrelator > >& corrs) : + mBw (bw ),//save bandwidth + dim (mBw + (mBw % 2 == 0 ? 3 : 2) ),//compute smallest odd dimension for bandwidth + quNp(backPrj->northPoleQuat() ),//get back projection rotation + wrk (imPrc->numPix() ),//allocate space for image processing + sph (dim * dim * 2 ),//allocate space for back projection + gln (mBw * mBw ),//allocate space for SHT of a single pattern + prc (std::move(imPrc) ),//copy image processor + prj (std::move(backPrj) ),//copy back projector + sht (dim, dim - 2, square::Layout::Legendre ) //build spherical harmonic transformer + { + pSym.reserve(corrs.size()); + xc .reserve(corrs.size()); + for(const std::unique_ptr< sphere::PhaseCorrelator >& ptr : corrs) { + pSym.push_back( std::vector< xtal::Quat >() );//place holder for psuedosymmetric equivalents + xc .push_back(ptr->clone()); + } + } + + //@brief : estimate a reasonable batch size + //@param bw: bandwidth + //@param nt: number of worker threads + //@param np: number of patterns to index + //@return : a reasonable batch size (a number of patterns that should take on the order of 1s to index + template + size_t Indexer::BatchEstimate(const size_t bw, const size_t nt, const size_t np) { + //first compute an estimate assuming a large number of patterns to index compared to the available thread count + const double bw3 = double(bw * bw * bw); + const double scl = bw3 * std::log(bw3);//complexity scaling is O(n^3 * ln(n^3)) + const double k = 1E-8;//scaling factor on my laptop (should be a good enough estimate for all modern computers) + const double tPat = scl * k;//estimate time per pattern per thread + const double pps = 1.0 / tPat;//number of patterns that can be indexed in 1 second on 1 thread + size_t batchSize = std::max(1, (size_t) (pps * 0.61803398874989484820458683436564));//start with a batch that takes ~1/golden ratio seconds (that way it won't sync with updates which could make early time estimates inaccurate) + + //next make sure that we don't have unused threads for small numbers of patterns + const size_t numBatch = (size_t) std::ceil(double(np) / batchSize);//total number of batches + if(numBatch < nt * nt) {//make sure there are enough batches for load balancing + double newBatch = double(np) / (nt * nt); + batchSize = (size_t) std::ceil(newBatch); + } + return batchSize; + } + + //@brief : index a single image + //@param pat: pointer to image to index in row major order + //@param n : number of results to keep (top n), user is responsible for making sure this is reasonable (extra points will be filled with an invalid phase) + //@param res: location to write results + //@param ref: true/false to use newton's method refinement/triquadratic interpolation + //@template Pix: pixel type of image (will be cast to Real) + //@note : multiple results can be written (currently there is an additional result per psuedosymmetric orientation to check) + template + template + void Indexer::indexImage(Pix* pat, Result*const res, const size_t n, const bool ref) { + //fill output with empty result + for(size_t i = 0; i < n; i++) { + res[i].corr = 0;//only keep something with a positive phase + res[i].phase = -1;//start with 'bad' phase + std::fill(res[i].qu, res[i].qu+4, Real(0)); + } + const Real iq = computeHarmonics(pat);//do image processing, back project to sphere, and compute spherical harmonic transform + + //loop over phases + xtal::Quat q0; + Real eu[3]; + for(size_t p = 0; p < pSym.size(); p++) { + //compute cross correlation with phase + Result r = correlate(p, gln.data(), ref); + r.iq = iq; + r.phase = (int)p; + + //save the result if it is good enough + size_t idx = std::distance(res, std::upper_bound(res, res+n, r));//determine where in sorted list of outputs this falls (should we keep it) + if(idx < n) {//this is good enough to bump at least 1 result of the list + for(size_t i = n-1; i != idx; i--) res[i] = res[i-1];//loop from back to front shifting results down one + res[idx] = r;//insert this result into the list + } + + //check pseudo symmetric misorientations + xtal::zyz2qu(r.qu, q0.data());//convert to quaternion + for(const xtal::Quat& q : pSym[p]) { + //compute psuedo-symmetric orientation (order may be wrong here) + //q0 is the quaternion to rotate from crystal -> sample + //psuedo sym is in crystal frame + //q may need to be conjugated depending on how they are input + xtal::Quat qp = q0 * q;//first apply psuedo symmetry (q) then registered orientation (q0) + xtal::qu2zyz(qp.data(), eu);//convert back to to euler angles + + //do refinement + r = refine(p, gln.data(), eu); + r.iq = iq; + + //save the result if it is good enough + idx = std::distance(res, std::upper_bound(res, res+n, r));//determine where in sorted list of outputs this falls (should we keep it) + if(idx < n) {//this is good enough to bump at least 1 result of the list + for(size_t i = n-1; i != idx; i--) res[i] = res[i-1];//loop from back to front shifting results down one + res[idx] = r;//insert this result into the list + } + } + } + + //loop over results converting to quaterions + for(size_t i = 0; i < n; i++) { + xtal::zyz2qu(res[i].qu, res[i].qu);//convert to quaternion + xtal::quat::mul(Indexer::quNp.data(), res[i].qu, res[i].qu);//correct for rotated detector frame + for(size_t j = 1; j < 4; j++) res[i].qu[j] = -res[i].qu[j];//conjugate result (crystal->sample to sample->crystal) + } + } + + //@brief : refine the orientation of a single image + //@param pat: pointer to image to refine in row major order + //@param res: location to write result (and read initial phase + orientation) + //@template Pix: pixel type of image (will be cast to Real) + template + template + void Indexer::refineImage(Pix* pat, Result& res) { + //compute spectra of pattern + const Real iq = computeHarmonics(pat);//do image processing, back project to sphere, and compute spherical harmonic transform + + //unconjugate result + xtal::Quat q0; + xtal::quat::conj(res.qu, q0.data()); + + //uncorrect for rotated detector frame + xtal::Quat npC; + xtal::quat::conj(Indexer::quNp.data(), npC.data()); + xtal::quat::mul (npC.data(), q0.data(), q0.data()); + + //convert to euler angles + Real eu[3]; + xtal::qu2zyz(q0.data(), eu);//convert to euler angles + + //do refinement + refine(res.phase, gln.data(), eu); + xtal::zyz2qu(eu, res.qu); + + //re-correct for rotated detector frame + xtal::quat::mul(Indexer::quNp.data(), res.qu, res.qu); + + //re-conjugate result + for(size_t i = 1; i < 4; i++) res.qu[i] = -res.qu[i]; + res.iq = iq; + } + + //@brief : process an image, unproject to the sphere, and compute spherical harmonic coefficients + //@param im: image to process + //@return : image quality + template + template + Real Indexer::computeHarmonics(Pix* im) { + Real iq; + prc->process(im, wrk.data()); + sum2 = prj->unproject(wrk.data(), sph.data(), &iq); + sht.analyze(sph.data(), gln.data(), mBw, mBw);//compute SHT of function in sph + return iq; + } + + //@brief : index the spherical function currently held in sph + //@param p : phase to correlate with + //@param gln: spectra of image to index + //@param ref: true/false to use newton's method refinement/triquadratic interpolation + //@return : correlation result (with ZYZ euler angle stored in qu[0,1,2]) + template + Result Indexer::correlate(const size_t p, std::complex const * gln, const bool ref) { + Result r; + r.corr = xc[p]->correlate(Indexer::gln.data(), r.qu, ref); + r.phase = (int)p; + return r; + } + + //@brief : refine the spherical function currently held in sph + //@param p : phase to refine with + //@param gln: spectra of image to refine + //@param eu : initial ZYZ euler angle to refine from + //@return : refinement result (with ZYZ euler angle stored in qu[0,1,2]) + template + Result Indexer::refine(const size_t p, std::complex const * gln, Real const * const eu) { + Result r; + std::copy(eu, eu+3, r.qu); + r.corr = xc[p]->refinePeak(gln, r.qu); + r.phase = (int)p; + return r; + } + +} + +#endif//_SPHERE_INDEXER_H_ + diff --git a/include/idx/master.hpp b/include/idx/master.hpp new file mode 100644 index 0000000..3abfe4d --- /dev/null +++ b/include/idx/master.hpp @@ -0,0 +1,640 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _MASTER_H_ +#define _MASTER_H_ + +#include "xtal/phase.hpp" +#include "xtal/quaternion.hpp" +#include "sht/square_sht.hpp" +#include "sht_file.hpp" + +namespace emsphinx { + + //@brief: base class for master pattern and master spectra + template + class MasterData { + protected: + std::vector< xtal::Quat > pSm;//list of pseudo-symmetric misorientations + xtal::Phase phs;//crystal phase + Real sig;//sample tilt + Real kv ;//accelerating voltage + + public: + //@brief : get a copy of the phase + //@return: phase + xtal::Phase phase() const {return phs;} + + //@brief : get a copy of the phase's point group + //@return: symmetry group + xtal::PointGroup pointGroup() const {return phs.pg;} + + //@brief: get pseudo-symmetric misorientations + const std::vector< xtal::Quat >& pseudoSym() const {return pSm;} + + //@brief : add a pseudo-symmetric misorientation + //@param qp: pseudo-symmetric misorientation to add as quaternion + void addPseudoSym(const xtal::Quat& qp) {pSm.push_back(qp);} + + //@brief : add a list of pseudo-symmetric misorientation from a file + //@param fn: name of angle file to add misorientations from + void addPseudoSym(std::string fn); + + //@brief : get tilt + //@return: sample tilt in degrees + Real getSig() const {return sig;} + + //@brief : get accelerating voltage + //@return: accelerating voltage in kV + Real getKv() const {return kv;} + }; + + //helper class to hold master pattern files + template + struct MasterPattern : public MasterData { + std::vector nh ;//north hemisphere of master pattern as square lambert projection + std::vector sh ;//north hemisphere of master pattern as square lambert projection + uint16_t dim;//side length of master pattern + square::Layout lyt;//grid type + + //@brief: construct an empty master pattern + MasterPattern(const size_t d = 0) : dim((uint16_t)d), lyt(square::Layout::Lambert), nh(d*d), sh(d*d) {} + + //@brief : construct a master pattern from an EMsoft output file + //@param fileName: name of EMsoft hdf5 file to read from + MasterPattern(std::string fileName) {read(fileName);} + + //@brief : check if a master pattern can be rescaled + //@return: true/false if the layout of the pattern can/can't be rescaled + bool canRescale() const {return square::Layout::Lambert == lyt;} + + //@brief : rescale a master pattern + //@param nDm: new side length of master pattern + //@return : this + //@note : throws an exception if !canRescale() + MasterPattern& resize(const size_t nDm); + + //@brief : convert from a square lambert to a square legendre grid + //@param nDm: new side length of master pattern + //@return : this + //@note : just bilinearly interpolates between grids + MasterPattern& toLegendre(const size_t nDm); + MasterPattern& toLegendre() {return toLegendre(dim);} + + //@brief : convert from a square legendre to a square lambert grid + //@param nDm: new side length of master pattern + //@return : this + //@note : just bilinearly interpolates between grids + MasterPattern& toLambert(const size_t nDm); + MasterPattern& toLambert() {return toLambert(dim);} + + //@brief : read master patterns from an EMsoft output file + //@param fileName: name of EMsoft hdf5 file to read from + void read(std::string fileName); + + //@brief : give a master pattern n fold symmetry about the z axis + //@param n: rotational order + //@param m: 0 for no mirror + // 1 for mirror at phi = i * 180 / n (e.g. 3m1 or -6m2) + // 2 for mirror at phi = i * 180 / n + 90 / n (i.e. -42m, 31m, -62m, -43m) + //@note : uses first 1/n of ring (axact for n == 2 or 4, approximate otherwise) + //@note : doesn't update crystal structure + void makeNFold(const size_t n, const int m = 0); + + //@brief : give a master pattern a mirror plane at the quator + //@note : doesn't update crystal structure + void makeZMir() {sh = nh;} + + //@brief : give a master pattern inversion symmetry + //@note : doesn't update crystal structure + void makeInvSym(); + + //@brief : copy the equator of a master pattern from the north to south hemisphere + void matchEquator(); + }; + + //helper struct to hold the spherical harmonic transform of a master pattern + template + class MasterSpectra : public MasterData { + uint16_t mBw;//maximum bandwidth of harmonic transform + std::vector< std::complex > alm;//harmonic transform coefficients with a^l_m at alm[bw * m + l] + + public: + //@brief: empty spectra + MasterSpectra() {} + + //@brief : construct a spectra from a master pattern + //@param mp : master pattern + //@param bw : desired bandwidth + //@param nrm: should the pattern be normalized (mean 0, stdev 1) before computing the SHT + //@return : spectra of square legendre pattern + //@note : dim must be odd, bandwidth is dim - 2 + MasterSpectra(MasterPattern mp, const uint16_t bw, const bool nrm = true); + + //@brief : construct a master pattern from an EMsoft output file + //@param fileName: name of EMsoft hdf5 file to read from + MasterSpectra(std::string fileName) {read(fileName);} + + //@brief : get a pointer to the spectra with (m,l) stored: (0,0), (0,1), (0,2), ..., (0,bw-1), (1,0), (1,1), (1,2), ..., (bw-1,0), (bw-1,1), (bw-1,bw-1) + //@return: pointer with a^l_m at ptr[bw * m + l] + std::complex * data() {return alm.data();} + std::complex const * data() const {return alm.data();} + + //@brief : repack the spectra into a new maximum bandwidth (0 padded for increasing size) + //@param bw: bandwidth of new spectra + //@return : this spectra (repacked) + MasterSpectra& resize(const uint16_t bw); + + //@brief : get rotational symmetry of the master pattern about the z axis + //@return: rotational symmetry + size_t nFold() const {return MasterData::pointGroup().zRot();} + + //@brief : check if there is a mirror plane at the equator of the master pattern + //@return: true/false if there is/isn't a mirror plane + bool mirror() const {return MasterData::pointGroup().zMirror();} + + //@brief : check if there is a inversion symmetry + //@return: true/false if there is/isn't inversion symmetry + bool invSym() const {return MasterData::pointGroup().inversion();} + + //@brief : get the maximum bandwidth + //@return: max bandwidth + size_t getBw() const {return mBw;} + + //@brief: remove DC value from spectra (make average value 0) + void removeDC() {alm[0] = std::complex(0);} + + //@brief : read master patterns from an EMSphInx harmonics file + //@param fileName: name of EMSphInx spx file to read from + void read(std::string fileName); + + }; + +}//emsphinx + +//////////////////////////////////////////////////////////////////////// +// Implementations // +//////////////////////////////////////////////////////////////////////// + +#include "H5Cpp.h" +#include "util/image.hpp" +#include "xtal/vendor/emsoft.hpp" + +namespace emsphinx { + + //@brief : add a list of pseudo-symmetric misorientation from a file + //@param fn: name of angle file to add misorientations from + template + void MasterData::addPseudoSym(std::string fn) { + emsoft::AngleFile af(fn); + if(xtal::Rotation::Quaternion != af.getType()) throw std::runtime_error("only quaternion angle files are supported for psuedo symmetry"); + xtal::Quat const* qu = (xtal::Quat const*)af.getAngles(); + const xtal::Quat ident(1, 0, 0, 0); + for(size_t i = 0; i < af.getNum(); i++) { + if(!(ident == qu[i])) { + addPseudoSym(qu[i]); + } + } + } + + //////////////////////////////////////////////////////////////////////// + // MasterPattern // + //////////////////////////////////////////////////////////////////////// + + //@brief : read master patterns from an EMsoft output file + //@param fileName: name of EMsoft hdf5 file to read from + template + void MasterPattern::read(std::string fileName) { + //first open the h5 file + H5::H5File file = H5::H5File(fileName.c_str(), H5F_ACC_RDONLY);//read only access + std::vector dims, dims2;//vector to hold dimensions of datasets + + //read accelerating voltage and sample tilt + double dVal; + file.openDataSet("/NMLparameters/MCCLNameList/sig" ).read(&dVal, H5::PredType::NATIVE_DOUBLE, H5::DataSpace(H5S_SCALAR)); MasterData::sig = (Real) dVal; + file.openDataSet("/NMLparameters/MCCLNameList/EkeV").read(&dVal, H5::PredType::NATIVE_DOUBLE, H5::DataSpace(H5S_SCALAR)); MasterData::kv = (Real) dVal; + + /* + //sanity check atom data + dims.resize(2, 0); + H5::DataSet xtal = file.openDataSet("CrystalData/AtomData"); + dims.resize(xtal.getSpace().getSimpleExtentNdims());//allocate space for AtomData (should be 2) + xtal.getSpace().getSimpleExtentDims(dims.data());//read extent in each dimension + if(2 != dims.size() || 5 != dims[0]) throw std::runtime_error("unexpected AtomData layout"); + */ + //construct phase from lattice parameters + space group + MasterData::phs.readMaster(file.openGroup("/CrystalData")); + // const size_t numAtoms = dims[1]; + + //read monte carlo simulation results + H5::DataSet energy = file.openDataSet("EMData/MCOpenCL/accum_e"); + dims.resize(energy.getSpace().getSimpleExtentNdims());//allocate space for accum_e (should be 3) + energy.getSpace().getSimpleExtentDims(dims.data());//read extent in each dimension + if(3 != dims.size()) throw std::runtime_error("unexpected accum_e layout"); + const size_t slicePts = dims[0] * dims[1]; + std::vector accumE(slicePts * dims[2]);//allocate space + energy.read(accumE.data(), H5::PredType::NATIVE_UINT32);//read accum_e + + //use results to determine energy averaging + std::vector eCounts(dims[2], 0); + for(size_t i = 0; i < slicePts; i++) std::transform(eCounts.cbegin(), eCounts.cend(), accumE.cbegin() + i * eCounts.size(), eCounts.begin(), std::plus()); + std::vector weights(eCounts.cbegin(), eCounts.cend()); + const Real sum = std::accumulate(weights.cbegin(), weights.cend(), Real(0)); + std::for_each(weights.begin(), weights.end(), [sum](Real& i){i /= sum;}); + + //check if we're in an EBSD or ECP file + int type = 0, idx = 0; + file.openGroup("EMData").iterateElems(".", &idx, [](int, const char* nm, void* pT)->int{ + if(0 == std::string("EBSDmaster").compare(nm)) *(int*)pT = 1; + if(0 == std::string("ECPmaster" ).compare(nm)) *(int*)pT = 2; + return 0; + }, &type); + + std::string mode; + if(1 == type) {//EBSD + mode = "EBSD"; + } else if(2 == type) {//ECP + mode = "ECP"; + } else { + throw std::runtime_error("couldn't determine if master pattern was EBSD or ECP"); + } + + //open master pattern arrays and sanity check dimensions + const std::string prefex = "EMData/" + mode + "master/"; + H5::DataSet nhData = file.openDataSet(prefex + "mLPNH");//northern hemisphere as square lambert + H5::DataSet shData = file.openDataSet(prefex + "mLPSH");//southern hemisphere as square lambert + dims .resize(nhData.getSpace().getSimpleExtentNdims());//allocate space for master patterns (should be 4) + dims2.resize(shData.getSpace().getSimpleExtentNdims());//allocate space for master patterns (should be 4) + nhData.getSpace().getSimpleExtentDims(dims .data());//read extent in each dimension + shData.getSpace().getSimpleExtentDims(dims2.data());//read extent in each dimension + if(dims != dims2) throw std::runtime_error("master pattern hemispehres are different shapes"); + if(1 == type) {//EBSD + //{atom, energy, x, y} + if(4 != dims.size()) throw std::runtime_error("unexpected master pattern layout"); + } else if(2 == type) {//ECP + //{atom, x, y} + if(3 != dims.size()) throw std::runtime_error("unexpected master pattern layout"); + dims.insert(dims.begin() + 1, 1);//{atom, 1, x, y} + weights = std::vector(1,1);//energy weights + } else { + throw std::runtime_error("couldn't determine if master pattern was EBSD or ECP"); + } + const size_t numAtoms = dims[0]; + // if(dims[0] != numAtoms) throw std::runtime_error("unexpected master pattern layout");//make sure number of patterns matches atom count + if(dims[1] != weights.size()) throw std::runtime_error("unexpected master pattern layout");//make sure energy bins matches MC output + + //save size / allocate data + dim = (uint16_t)dims[2];//extract master pattern side length + nh = std::vector(size_t(dim) * size_t(dim), 0); + sh = std::vector(size_t(dim) * size_t(dim), 0); + lyt = square::Layout::Lambert; + + //now read entire master pattern + const size_t hemPts = dims[2] * dims[3];//points for one hemisphere + const size_t atmPts = dims[1] * hemPts ;//points for all energy bins of a single atom + std::vector nhPat(dims[0] * atmPts), shPat(dims[0] * atmPts);//allocate space + nhData.read(nhPat.data(), H5::PredType::NATIVE_FLOAT);//read north hemisphere + shData.read(shPat.data(), H5::PredType::NATIVE_FLOAT);//read south hemisphere + + //add together all atoms for each energy + for(size_t i = 1; i < numAtoms; i++) {//loop over each atom array accumulating + std::transform(nhPat.cbegin(), nhPat.cbegin() + atmPts, nhPat.cbegin() + i * atmPts, nhPat.begin(), std::plus()); + std::transform(shPat.cbegin(), shPat.cbegin() + atmPts, shPat.cbegin() + i * atmPts, shPat.begin(), std::plus()); + } + + //finaly compute energy weighted average + for(size_t i = 0; i < weights.size(); i++) { + const Real w = weights[i]; + auto func = [w](const Real& a, const float& b){return a + w * b;}; + std::transform(nh.cbegin(), nh.cend(), nhPat.cbegin() + hemPts * i, nh.begin(), func); + std::transform(sh.cbegin(), sh.cend(), shPat.cbegin() + hemPts * i, sh.begin(), func); + } + } + + //@brief : rescale a master pattern + //@param nDm: new side length of master pattern + //@return : this + //@note : throws an exception if !canRescale() + template + MasterPattern& MasterPattern::resize(const size_t nDm) { + if(!canRescale()) throw std::runtime_error("can't rescale non-lambert master patterns"); + if(nDm == dim) return *this; + + //rescale hemispheres into new storage + std::vector nhScaled(nDm*nDm), shScaled(nDm*nDm);//allocate space for scaled patterns + image::Rescaler sclr(dim, dim, Real(nDm) / dim, fft::flag::Plan::Estimate);//build rescaler, we're only rescaling as a prestep so don't waste time planning + sclr.scale(nh.data(), nhScaled.data(), false);//rescale north hemisphere + sclr.scale(sh.data(), shScaled.data(), false);//rescale south hemisphere + + //correct fftw rescaling + const Real factor = Real(0.5) / nhScaled.size(); + std::for_each(nhScaled.begin(), nhScaled.end(), [factor](Real& v){v *= factor;}); + std::for_each(shScaled.begin(), shScaled.end(), [factor](Real& v){v *= factor;}); + + //update members + nh.swap(nhScaled); + sh.swap(shScaled); + dim = (uint16_t) nDm; + return *this; + } + + //@brief : convert from a square lambert to a square legendre grid + //@param nDm: new side length of master pattern + //@note : just bilinearly interpolates between grids + //@return : this + template + MasterPattern& MasterPattern::toLegendre(const size_t nDm) { + //sanity check grid type + if(square::Layout::Legendre == lyt) return *this;//already legendre + else if(square::Layout::Lambert != lyt) throw std::logic_error("unknown square grid type"); + + //rescale larger for better interpolation + const size_t dimScaled = (size_t)std::round(std::sqrt(Real(2)) * nDm); + resize(dimScaled);//rescale pattern if needed + + //compute normals of square legendre grid + const size_t nPts = nDm * nDm; + std::vector xyz(nPts * 3);//allocate space + square::legendre::normals(nDm, xyz.data());//compute normals for northern hemisphere + + //interpolate master pattern on legendre grid points + std::vector lgNh(nPts), lgSh(nPts);//allocate space for north/south hemispheres of legendre grids + for(size_t i = 0; i < nPts; i++) { + //square lambert project to [0, dim - 1] + Real XY[2]; + Real const * const pt = xyz.data() + 3 * i; + square::lambert::sphereToSquare(pt[0], pt[1], pt[2], XY[0], XY[1]); + + //bilinear interpolate from master pattern + image::BiPix p; + p.bilinearCoeff(XY[0], XY[1], dimScaled, dimScaled); + lgNh[i] = p.interpolate(nh.data()); + lgSh[i] = p.interpolate(sh.data()); + } + + //update members + nh.swap(lgNh); + sh.swap(lgSh); + lyt = square::Layout::Legendre; + dim = (uint16_t)nDm; + return *this; + } + + //@brief : convert from a square legendre to a square lambert grid + //@param nDm: new side length of master pattern + //@note : just bilinearly interpolates between grids + //@return : this + template + MasterPattern& MasterPattern::toLambert(const size_t nDm) { + //sanity check grid type + if(square::Layout::Lambert == lyt) return *this;//already lambert + else if(square::Layout::Legendre != lyt) throw std::logic_error("unknown square grid type"); + const bool even = 0 == (dim % 2); + + //compute cosines of legendre grid once + std::vector cLat = square::cosLats(dim, square::Layout::Legendre); + std::vector xyz = square::normals(dim, square::Layout::Legendre); + + //allocate space for north/south hemispheres of legendre grids + const size_t nPts = nDm * nDm; + std::vector lmNh(nPts), lmSh(nPts); + + //loop over new grid points bilinearly interpolating + Real XY[2]; + for(size_t j = 0; j < nDm; j++) {//loop over rows of square lambert grid + XY[1] = Real(j) / (nDm - 1);//fractional y position + const Real aY = std::fabs(XY[1] - Real(0.5));//convert from [0,1] to | [-0.5,0.5] | + for(size_t i = 0; i < nDm; i++) {//loop over + XY[0] = Real(i) / (nDm - 1);//fractional x position + + //now determing indices of bounding points on legendre grid + Real n[3]; + size_t inds[4]; + square::lambert::squareToSphere(XY[0], XY[1], n[0], n[1], n[2]); + square::legendre::boundingInds(dim, cLat.data(), n, inds); + + //next determine the 3 nearest points + const Real dots[4] = { + std::inner_product(n, n+3, xyz.data() + 3 * inds[0], Real(0)), + std::inner_product(n, n+3, xyz.data() + 3 * inds[1], Real(0)), + std::inner_product(n, n+3, xyz.data() + 3 * inds[2], Real(0)), + std::inner_product(n, n+3, xyz.data() + 3 * inds[3], Real(0)), + }; + + //for now just nearest neighbor interpolate + //in the future this should probably switch to barycentric between 3 closests points + const size_t idx = std::distance(dots, std::max_element(dots, dots+4)); + lmNh[j*nDm+i] = nh[inds[idx]]; + lmSh[j*nDm+i] = sh[inds[idx]]; + } + } + + //update members + nh.swap(lmNh); + sh.swap(lmSh); + lyt = square::Layout::Lambert; + dim = (uint16_t)nDm; + return *this; + } + + //@brief : give a master pattern n fold symmetry about the z axis + //@param n: rotational order + //@param m: 0 for no mirror + // 1 for mirror at phi = i * 180 / n (e.g. 3m1 or -6m2) + // 2 for mirror at phi = i * 180 / n + 90 / n (i.e. -42m, 31m, -62m, -43m) + //@note : uses first 1/n of ring (axact for n == 2 or 4, approximate otherwise) + //@note : doesn't update crystal structure + template + void MasterPattern::makeNFold(const size_t n, const int m) { + if(0 == dim % 2) throw std::runtime_error("must be odd side length"); + std::function doHemi = [this,n,m](Real*const hemi) { + const size_t rings = (this->dim + 1) / 2; + for(size_t i = 1; i < rings; i++) { + std::vector work(4 * (this->dim - 1));//large enough to hold equator + const size_t count = square::readRing(this->dim, i, hemi, work.data());//extract nth ring + const double repeatNum = double(count) / n; + const size_t iRepeatNum = (size_t)repeatNum; + const size_t iRepeatNum14 = (size_t)(repeatNum * 0.25); + const size_t iRepeatNum24 = (size_t)(repeatNum * 0.50); + const size_t iRepeatNum34 = (size_t)(repeatNum * 0.75); + + if(0 == m) { + //no mirror + } else if(1 == m) { + //mirror at phi = i * 180 / n + std::reverse_copy(work.begin() , work.begin() + iRepeatNum24, work.begin() + iRepeatNum24); + } else if(2 == m) { + //mirror at phi = i * 180 / n + 90 / n + std::reverse_copy(work.begin() , work.begin() + iRepeatNum14, work.begin() + iRepeatNum14); + std::reverse_copy(work.begin() + iRepeatNum24, work.begin() + iRepeatNum34, work.begin() + iRepeatNum34); + } else throw std::runtime_error("invalid m"); + + for(size_t j = 1; j < n; j++) { + const size_t start = (size_t)std::round(repeatNum * j); + std::copy(work.begin(), work.begin() + iRepeatNum, work.begin() + start); + } + square::writeRing(this->dim, i, hemi, work.data());//replace nth ring + } + }; + doHemi(nh.data()); + doHemi(sh.data()); + } + + //@brief : give a master pattern inversion symmetry + //@note : doesn't update crystal structure + template + void MasterPattern::makeInvSym() { + for(size_t j = 0; j < dim; j++) { + for(size_t i = 0; i < dim; i++) { + sh[(dim - 1 - j) * dim + (dim - 1 - i)] = nh[dim * j + i]; + } + } + } + + //@brief : copy the equator of a master pattern from the north to south hemisphere + template + void MasterPattern::matchEquator() { + std::copy(nh. begin(), nh. begin() + dim, sh. begin()); + for(size_t i = 1; i < dim-1; i++) { + sh[dim* i ] = nh[dim* i ]; + sh[dim*(i+1)-1] = nh[dim*(i+1)-1]; + } + std::copy(nh.rbegin(), nh.rbegin() + dim, sh.rbegin()); + } + + //////////////////////////////////////////////////////////////////////// + // MasterSpectra // + //////////////////////////////////////////////////////////////////////// + + //@param mp : master pattern + //@param bw : desired bandwidth + //@param nrm: should the pattern be normalized (mean 0, stdev 1) before computing the SHT + //@return : spectra of square legendre pattern + //@note : dim must be odd, bandwidth is dim - 2 + template + MasterSpectra::MasterSpectra(MasterPattern mp, const uint16_t bw, const bool nrm) { + const uint16_t dimLg = bw + 2 + (bw % 2 == 0 ? 1 : 0);//compute side length of legendre grid for target bandwidth (must be odd) + if(square::Layout::Legendre != mp.lyt) {//convert grid type if needed + mp.toLegendre(dimLg); + } + + const size_t dim = mp.dim; + if(nrm) { + //compute pixels sizes relative to average area + if(dim < dimLg) throw std::runtime_error("insufficient grid resolution for requested bandwidth"); + std::vector weights(dim * dim); + std::vector omega = square::solidAngles(dim, square::Layout::Legendre); + for(size_t i = 0; i < weights.size(); i++) weights[i] = omega[square::ringNum(dim, i)]; + + //correct weights for double counting of equator + for(size_t i = 0; i < dim; i++) weights[i ] /= 2; + for(size_t i = 0; i < dim; i++) weights[i * dim ] /= 2; + for(size_t i = 0; i < dim; i++) weights[i * dim + dim - 1 ] /= 2; + for(size_t i = 0; i < dim; i++) weights[i + dim * (dim - 1)] /= 2; + const Real totW = std::accumulate(weights.cbegin(), weights.cend(), Real(0)); + + //make area weighted average 0 + const Real mean = ( std::inner_product(weights.cbegin(), weights.cend(), mp.nh.cbegin(), Real(0)) + + std::inner_product(weights.cbegin(), weights.cend(), mp.sh.cbegin(), Real(0)) ) / totW ; + std::for_each(mp.nh.begin(), mp.nh.end(), [mean](Real& v){v -= mean;}); + std::for_each(mp.sh.begin(), mp.sh.end(), [mean](Real& v){v -= mean;}); + + //make the area weighted standard deviation 1 + std::function multOp = [mean](const Real& v, const Real& w){return v * v * w;};//lambda for inner product multiplication + const Real sumNh = std::inner_product(mp.nh.cbegin(), mp.nh.cend(), weights.cbegin(), Real(0), std::plus(), multOp); + const Real sumSh = std::inner_product(mp.sh.cbegin(), mp.sh.cend(), weights.cbegin(), Real(0), std::plus(), multOp); + const Real stdev = std::sqrt((sumNh + sumSh) / (totW * 2)); + std::for_each(mp.nh.begin(), mp.nh.end(), [stdev](Real& v){v /= stdev;}); + std::for_each(mp.sh.begin(), mp.sh.end(), [stdev](Real& v){v /= stdev;}); + } + + //compute SHT + if(0 == dim % 2) throw std::runtime_error("only odd side lengths are supported"); + MasterData::phs = mp.phase (); + MasterData::kv = mp.getKv (); + MasterData::sig = mp.getSig(); + mBw = bw; + alm.resize(size_t(mBw) * size_t(mBw)); + square::DiscreteSHT::Legendre(dim).analyze(mp.nh.data(), mp.sh.data(), alm.data(), mBw, mBw); + // alm[0] = std::complex(0);//make sure the mean is truly zero + } + + //@brief : repack the spectra into a new maximum bandwidth (0 padded for increasing size) + //@param bw: bandwidth of new spectra + //@return : repacked spectra + template + MasterSpectra& MasterSpectra::resize(const uint16_t bw) { + if(bw > mBw) {//zero pad up + alm.resize(bw * bw, 0); + for(size_t m = bw - 1; m < bw; m--) { + std::copy(alm.begin() + m * mBw, alm.begin() + m * mBw + mBw, alm.begin() + m * bw);//move + std::fill(alm.begin() + m * bw + mBw, alm.begin() + m * bw + bw, std::complex(0));//zero pad + } + } else if(bw < mBw) {//crop down + for(size_t m = 1; m < bw; m++) std::copy(alm.begin() + m * mBw, alm.begin() + m * mBw + bw, alm.begin() + m * bw);//move + alm.resize(bw * bw, 0); + } + mBw = bw; + return *this; + } + + //@brief : read master patterns from an EMSphInx harmonics file + //@param fileName: name of EMSphInx spx file to read from + template + void MasterSpectra::read(std::string fileName) { + + //read file into spx structure + sht::File file; + std::ifstream is(fileName, std::ios::in | std::ios::binary); + file.read(is); + + //extract relevant header data + MasterData::kv = (Real) file.header.beamEnergy (); + MasterData::sig = (Real) file.header.primaryAngle(); + mBw = file.harmonics.bw (); + MasterData::phs.pg = xtal::PointGroup(file.material.sgEff ()); + MasterData::phs.name.clear(); + MasterData::pSm.clear(); + if(1 == file.material.numXtal()) { + for(size_t i = 0; i < 6; i++) MasterData::phs.lat[i] = (Real) file.material.xtals[0].lat()[i]; + } + + //resize data + uncompress + alm.resize(mBw * mBw, 0); + file.harmonics.unpackHarm(file.harmonics.alm.data(), alm.data()); + } + +}//emsphinx + +#endif//_MASTER_H_ diff --git a/include/idx/roi.h b/include/idx/roi.h new file mode 100644 index 0000000..6ccae3c --- /dev/null +++ b/include/idx/roi.h @@ -0,0 +1,632 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _ROI_H_ +#define _ROI_H_ + +#include +#include + +namespace emsphinx { + + enum class DrawMode { + Rectangle, + Ellipse , + Polygon , + }; + + class RoiSelection { + DrawMode mode = DrawMode::Rectangle;//type of ROI selection + bool mkSq = false ;//should the aspect ratio be fixed (rectangle / ellipse only) + std::vector< std::pair > pts ;//selection coordinates + int curPt = -1 ;//point currently being manipulated (or -1 for none) + bool inv = false ;//is the ROI selection inverted (exclude selected instead of include) + + public: + //drawing mode independent + + //@brief : check if coordinates are near an existing point + //@param x : coordinates to check + //@param y : coordinates to check + //@param r2: square radius cutoff + //@return : index of nearby point (-1 if none are nearby) + int nearPt(const int x, const int y, const int r2) const; + + //@brief: determine if a point is currently selected + bool hasSelection() const {return -1 == curPt;} + + //@brief : try to select a point + //@param x : coordinates to try selecting near + //@param y : coordinates to try selecting near + //@param r2: square radius cutoff + //@return : true if a point was selected + bool trySelect(const int x, const int y, const int r2) {curPt = nearPt(x, y, r2); return -1 != curPt;} + + //@brief: clear current shape + //@return: true if anything changed, false otherwise + bool clear(); + + //@brief : get the drawing mode + //@return: drawing mode + DrawMode getMode() const {return mode;} + + //@brief : chamge the drawing mode + //@return: true if the mode changed, false otherwise + bool changeMode(const DrawMode dm); + + //@brief : set fixed aspect ratio + //@param b: true to fix aspect ratio, false to free + void setFixedAspect(const bool b) {mkSq = b;} + + //@brief : get current coordinate list + //@return: read only access to list + const std::vector< std::pair >& getPts() const {return pts;} + + //@brief : check if there is a complete shape + //@return: true if there is a complete shape, false otherwise + const bool hasShape() const {return pts.empty() ? false : DrawMode::Polygon == mode ? (pts.front() == pts.back() && pts.size() > 2 && curPt + 1 < pts.size()) : true;} + + //@brief : translate + //@param dX: x shift + //@param dY: y shift + void translatePoints(const int dX, const int dY) {for(std::pair& pt : pts) {pt.first += dX, pt.second += dY;}} + + //@brief : update coordinates of a point + //@param idx: point to update + //@param x : new x coordiante + //@param y : new y coordiante + void movePoint(const size_t idx, const int x, const int y); + + //rectangle building + + //@brief : start drawing a new rectangle + //@param x: x origin of new rectangle + //@param y: y origin of new rectangle + void startRect(const int x, const int y); + + //@brief : start drawing a new rectangle + //@param ori: origin of new rectangle + void finishRect() {curPt = -1;} + + //@brief : resize a rectangle + //@param x: new x coordinate + //@param y: new y coordinate + //@return : true if anything changed, false otherwise + bool resizeRect(const int x, const int y); + + //@brief : stop resizing a rectangle + //@param pt : new coordinate + void ungrabRect() {curPt = -1; if(pts.empty() ? true : pts.front() == pts.back()) clear();} + + //polygon building + + //@brief : start drawing a polygon + //@param x: x origin of polygon + //@param y: y origin of polygon + void startPoly(const int x, const int y); + + //@brief: check if we are currently building a polygon + bool buildingPoly() const {return pts.empty() ? false : ( 2 == pts.size() ? true : pts.back() != pts.front() );} + + //@brief : add a new point to an existing polygon + //@param x : x coordinate of new point to add + //@param y : y coordinate of new point to add + //@param r2: cutoff radius^2 for 2 points being the same + //@note : finishes the polygon if pt is sufficiently close to the first point + //@return : true if polygon was closed, false otherwise + bool addPolyPt(const int x, const int y, const int r2); + + //@brief : remove currently selected point from an existing polygon + //@return: true if point was removed, false otherwise + bool remPolyPt(); + + //@brief : duplicated currently selected point in an existing polygon + //@param delta: offset from previous point + //@return : true if a point was duplicate, false otherwise + bool dupPolyPt(const int delta); + + //@brief : finish drawing a polygon + //@return : true if polygon was closed, false otherwise + bool finishPoly(); + + //@brief : move a polygon vertex + //@param x : new x coordinate + //@param y : new y coordinate + //@return : true if polygon was closed, false otherwise + bool resizePoly(const int x, const int y); + + //@brief : stop resizing a polygon + //@param pt : new coordinate + void ungrabPoly() {if(!buildingPoly()) curPt = -1;} + + //@brief : build an image mask for inside / outside the selection area + //@param w: image width + //@param h: image height + std::vector buildMask(const size_t w, const size_t h) const; + + //@brief : check if a point is inside or outside the selection + //@param x: x coordinate to check + //@param y: y coordinate to check + //@return : true/false if (x,y) is inside/outside the shape (false if no shape) + bool inside(const int x, const int y) const; + + //@brief : convert an ROI to a string + //@return: string representation + std::string to_string() const; + + //@brief : parse an ROI from a string + //@param str: string representation + void from_string(std::string str); + + //@brief : get if the mask is inverted or not + //@return: true if the ROI describes the exluded section, false if it describes the included section + bool getInv() const {return inv;} + + //@brief : set if the mask is inverted or not + //@param i: true if the ROI should describe the exluded section, false if it should describe the included section + void setInv(const bool& i) {inv = i;} + + }; + +}//emsphinx + +#include +#include +#include + +namespace emsphinx { + + //@brief : check if coordinates are near an existing point + //@param x : coordinates to check + //@param y : coordinates to check + //@param r2: square radius cutoff + //@return : index of nearby point (-1 if none are nearby) + int RoiSelection::nearPt(const int x, const int y, const int r2) const { + if(pts.empty()) return -1; + //loop over existing points finding closest one + size_t nrPt = 0; + int rMin = std::numeric_limits::max(); + for(size_t i = 0; i < pts.size(); i++) { + const int dx = pts[i].first - x; + const int dy = pts[i].second - y; + const int rr = dx*dx + dy*dy; + if(rr < rMin) { + nrPt = i; + rMin = rr; + } + } + return rMin < r2 ? int(nrPt) : -1; + } + + //@brief : clear current shape + //@return: true if anything changed, false otherwise + bool RoiSelection::clear() { + inv = false; + if(!pts.empty()) { + pts.clear(); + curPt = -1; + return true; + } + return false; + } + + //@brief : chamge the drawing mode + //@return: true if the mode changed, false otherwise + bool RoiSelection::changeMode(const DrawMode dm) { + if(mode != dm) { + clear(); + mode = dm; + return true; + } + return false; + } + + //@brief : update coordinates of a point + //@param idx: point to update + //@param x : new x coordiante + //@param y : new y coordiante + void RoiSelection::movePoint(const size_t idx, const int x, const int y) { + if(pts.size() > 3 && !buildingPoly() && (0 == idx || pts.size() - 1 == idx)) { + pts[0].first = x; + pts[0].second = y; + pts.back() = pts.front(); + } else { + pts[idx].first = x; + pts[idx].second = y; + } + } + + //rectangle building + + //@brief : start drawing a new rectangle + //@param x: x origin of new rectangle + //@param y: y origin of new rectangle + void RoiSelection::startRect(const int x, const int y) { + pts.assign(2, std::pair(x, y)); + curPt = 1; + } + + //@brief : resize a rectangle + //@param x: new x coordinate + //@param y: new y coordinate + //@return : true if anything changed, false otherwise + bool RoiSelection::resizeRect(const int x, const int y) { + if(!pts.empty() && -1 != curPt) { + std::pair pt(x, y); + if(mkSq) { + int dx = x - pts[1-curPt].first ; + int dy = y - pts[1-curPt].second; + if(std::abs(dx) > std::abs(dy)) { + dy = dy < 0 ? -1 : 1; + dy *= std::abs(dx); + } else { + dx = dx < 0 ? -1 : 1; + dx *= std::abs(dy); + } + pt = pts[1-curPt]; + pt.first += dx; + pt.second += dy; + } + pts[curPt] = pt; + return true; + } + return false; + } + + //polygon building + + //@brief : start drawing a polygon + //@param x: x origin of polygon + //@param y: y origin of polygon + void RoiSelection::startPoly(const int x, const int y) { + pts.assign(2, std::pair(x, y)); + curPt = 1; + } + + //@brief : add a new point to an existing polygon + //@param x : x coordinate of new point to add + //@param y : y coordinate of new point to add + //@param r2: cutoff radius^2 for 2 points being the same + //@note : finishes the polygon if pt is sufficiently close to the first point + //@return : true if polygon was closed, false otherwise + bool RoiSelection::addPolyPt(const int x, const int y, const int r2) { + if(buildingPoly()) { + //check if this point is close enough to first point + int dx = x - pts.front().first ; + int dy = y - pts.front().second; + const bool finish = pts.size() > 2 && (dx*dx + dy * dy) < r2; + + if(finish) {//close polygon if near first point + finishPoly(); + curPt = -1; + return true; + } else {//otherwise add new point + dx = pts.back().first - pts[pts.size() - 2].first ; + dy = pts.back().second - pts[pts.size() - 2].second; + const bool dup = (dx*dx + dy * dy) < r2;//is the last and 2nd to last point the same? + if(!dup) pts.push_back(std::pair(x, y));//don't add duplicate points (eg on up/down of mouse click) + curPt = int(pts.size()) - 1; + return false; + } + } + return false; + } + + //@brief : remove currently selected point from an existing polygon + //@return: true if point was removed, false otherwise + bool RoiSelection::remPolyPt() { + if(-1 == curPt) return false; + if(buildingPoly() && pts.size() > 2 ) {//currently building and can remove + if(0 != curPt) std::swap(pts[curPt], pts[curPt-1]); + pts.erase(pts.begin() + curPt); + if(-1 != curPt) --curPt; + return true; + } else if(pts.size() > 4) {//completed and can remove + pts.erase(pts.begin() + curPt); + if(0 == curPt) pts.back() = pts.front(); + else if(pts.size() == curPt) pts.front() = pts.back(); + curPt = -1; + return true; + } + return false; + } + + //@brief : duplicated currently selected point in an existing polygon + //@param delta: offset from previous point + //@return : true if a point was duplicate, false otherwise + bool RoiSelection::dupPolyPt(const int delta) { + if(-1 == curPt) return false;//no point selected + if(buildingPoly()) return false;//finish building first + + //determine line direction from previous point to current point + int idxPrev = (curPt == 0) ? int(pts.size()) - 2 : curPt - 1; + const int dx = pts[curPt].first - pts[idxPrev].first ; + const int dy = pts[curPt].second - pts[idxPrev].second; + const int r2 = dx*dx + dy*dy; + pts.insert(pts.begin() + curPt, pts[curPt]); + ++curPt; + if(r2 > 0) { + double rr = std::sqrt(double(r2)); + pts[curPt].first += (int)std::round( double(dx * delta) / rr ); + pts[curPt].second += (int)std::round( double(dy * delta) / rr ); + } + return true; + } + + //@brief : finish drawing a polygon + //@return : true if polygon was closed, false otherwise + bool RoiSelection::finishPoly() { + if(buildingPoly()) {//are we currently building a polygon? + if(pts.back() != pts.front()) pts.back() = pts.front();//pts.push_back(pts.front());//add closing point if needed (TODO check) + if(pts.size() < 4) pts.clear();//need at least a triangle + curPt = -1; + return true; + } + return false; + } + + //@brief : move a polygon vertex + //@param x : new x coordinate + //@param y : new y coordinate + //@return : true if polygon was closed, false otherwise + bool RoiSelection::resizePoly(const int x, const int y) { + if(!pts.empty() && -1 != curPt) { + std::pair pt(x, y); + + if(mkSq) { + int dx = x - pts[curPt - 1].first ; + int dy = y - pts[curPt - 1].second; + if(std::abs(dx) > std::abs(dy)) dy = 0; else dx = 0; + pt.first = pts[curPt - 1].first + dx; + pt.second = pts[curPt - 1].second + dy; + } + pts[curPt] = pt; + if(0 == curPt) pts.back() = pt;//keep closed if needed + return true; + } + return false; + } + + //@brief : check if point c is above, below, or on the line defined by a ==> b + //@param ax: x coordinate of point a + //@param ay: y coordinate of point a + //@param bx: x coordinate of point b + //@param by: y coordinate of point b + //@param cx: x coordinate of point c + //@param cy: y coordinate of point c + //@return : a value >0, <0, ==0 for above, below, and on respectively + //@note : this isn't robust against roundoff errors if switched to floating point + int orient2(int ax, int ay, int bx, int by, int cx, int cy) {return ( (bx - ax) * (cy - ay) - (cx - ax) * (by - ay) );} + + //@brief : build an image mask for inside / outside the selection area + //@param w: image width + //@param h: image height + std::vector RoiSelection::buildMask(const size_t w, const size_t h) const { + //handle empty shape + std::vector mask(w * h, inv ? 1 : 0);//empty mask + if(pts.empty()) return mask;//no points + + //start by getting bounding box of shape + int xMin = pts.front().first , yMin = pts.front().second; + int xMax = pts.front().first , yMax = pts.front().second; + for(const std::pair& pt : pts) { + if(pt.first < xMin) xMin = pt.first ; + if(pt.second < yMin) yMin = pt.second; + if(pt.first > xMax) xMax = pt.first ; + if(pt.second > yMax) yMax = pt.second; + } + + //keep indices inside image bounds + xMin = std::max(0, std::min(xMin, int(w))); + yMin = std::max(0, std::min(yMin, int(h))); + xMax = std::max(0, std::min(xMax, int(w))); + yMax = std::max(0, std::min(yMax, int(h))); + if(xMin == xMax || yMin == yMax) return mask;//no area inside image + + //now handle shapes specifically + switch(mode) { + case DrawMode::Rectangle://bounding box is mask + for(int j = yMin; j < yMax; j++) std::fill(mask.begin() + j * w + xMin, mask.begin() + j * w + xMax, inv ? 0 : 1); + break; + + case DrawMode::Ellipse : {//do ellipse check for pixels within mask + //compute center of ellipse once + int x0 = pts.front().first + pts.back().first ;//2 * x center + int y0 = pts.front().second + pts.back().second;//2 * y center + int aa = pts.front().first - pts.back().first ;//2 * x axis length + int bb = pts.front().second - pts.back().second;//2 * y axis length + aa *= aa;//4 * a^2 + bb *= bb;//4 * b^2 + for(int j = yMin; j < yMax; j++) { + int dy = j*2 - y0;//2*y distance from center + for(int i = xMin; i < xMax; i++) { + int dx = i*2 - x0;//2*x distance from center + double rr = double(dx * dx) / aa + double(dy * dy) / bb; + if(rr <= 1.0) mask[j * w + i] = inv ? 0 : 1; + } + } + } break; + + case DrawMode::Polygon ://compute winding number of pixels within mask + for(int j = yMin; j < yMax; j++) { + for(int i = xMin; i < xMax; i++) { + + //compute winding number of i,j + //logic from http://geomalgorithms.com/a03-_inclusion.html + int wn = 0; + for(size_t k = 1; k < pts.size(); k++) { + if(pts[k-1].second <= j) {// start j <= j + if(pts[k].second > j)// an upward crossing + if(orient2(pts[k-1].first , pts[k-1].second, pts[k].first , pts[k].second, i, j) > 0) // P left of edge + ++wn; // have a valid up intersect + } else {// start j > j (no test needed) + if(pts[k].second <= j) // a downward crossing + if(orient2(pts[k-1].first , pts[k-1].second, pts[k].first , pts[k].second, i, j) < 0) // P right of edge + --wn; // have a valid down intersect + } + } + if(wn != 0) mask[j * w + i] = inv ? 0 : 1; + } + } + break; + } + return mask; + } + + //@brief : check if a point is inside or outside the selection + //@param x: x coordinate to check + //@param y: y coordinate to check + //@return : true/false if (x,y) is inside/outside the shape (false if no shape) + bool RoiSelection::inside(const int x, const int y) const { + if(pts.empty()) return inv;//no shape + + //now handle shapes specifically + switch(mode) { + case DrawMode::Rectangle: {//bounding box is mask + const int xL = std::min(pts.front().first , pts.back().first ); + const int yB = std::min(pts.front().second, pts.back().second); + const int xR = std::max(pts.front().first , pts.back().first ); + const int yT = std::max(pts.front().second, pts.back().second); + return (x > xL && x < xR && y > yB && y < yT) ? !inv : inv; + } break; + + case DrawMode::Ellipse : {//do ellipse check for pixels within mask + const int xL = std::min(pts.front().first , pts.back().first ); + const int yB = std::min(pts.front().second, pts.back().second); + const int xR = std::max(pts.front().first , pts.back().first ); + const int yT = std::max(pts.front().second, pts.back().second); + if(! ( x > xL && x < xR && y > yB && y < yT) ) return inv;//outside bounding box + + + const int x0 = xR + xL;//2*x center + const int y0 = yT + yB;//2*y center + const int a = xR - xL;//2*x axis length + const int b = yT - yB;//2*y axis length + const int dx = x * 2 - x0; + const int dy = y * 2 - y0; + const double rr = double(dx * dx) / (a * a) + double(dy * dy) / (b * b); + return (rr <= 1.0) ? !inv : inv; + } break; + + case DrawMode::Polygon ://compute winding number of pixels within mask + //compute winding number of x, y + //logic from http://geomalgorithms.com/a03-_inclusion.html + int wn = 0; + for(size_t i = 1; i < pts.size(); i++) { + if(pts[i-1].second <= y) {// start y <= y + if(pts[i].second > y)// an upward crossing + if(orient2(pts[i-1].first , pts[i-1].second, pts[i].first , pts[i].second, x, y) > 0) // P left of edge + ++wn; // have a valid up intersect + } else {// start y > y (no test needed) + if(pts[i].second <= y) // a downward crossing + if(orient2(pts[i-1].first , pts[i-1].second, pts[i].first , pts[i].second, x, y) < 0) // P right of edge + --wn; // have a valid down intersect + } + } + return (wn != 0) ? !inv : inv; + break; + } + return inv; + } + + //@brief : convert an ROI to a string + //@return: string representation + std::string RoiSelection::to_string() const { + if(!hasShape()) return ""; + std::ostringstream ss; + if(inv) ss << 'i'; + switch(mode) { + case DrawMode::Ellipse : ss << 'e'; + case DrawMode::Rectangle: + if(2 != pts.size()) return ""; + ss << pts[0].first << ", " << pts[0].second << ", "; + ss << pts[1].first - pts[0].first << ", " << pts[1].second - pts[0].second; + break; + + case DrawMode::Polygon : + for (size_t i = 0; i < pts.size(); i++) { + ss << pts[i].first << ", " << pts[i].second; + if (pts.size() != i + 1) ss << ", "; + } + break; + } + return ss.str(); + } + + //@brief : parse an ROI from a string + //@param str: string representation + void RoiSelection::from_string(std::string str) { + clear(); + if(str.empty()) return; + if("0" == str) return; + std::istringstream ss(str); + mode = DrawMode::Rectangle; + if('i' == ss.peek()) { + inv = true; + char skip = ss.get(); + } + if('e' == ss.peek()) { + mode = DrawMode::Ellipse; + char skip = ss.get(); + } + + char c = ','; + int i; + std::vector v; + while(ss >> i) { + v.push_back(i); + if(ss >> c) { + if(',' != c) throw std::runtime_error("expected ',' between coordinates in ROI string"); + } + } + if(0 != v.size() % 2) throw std::runtime_error("odd number of points in ROI string"); + if(4 == v.size()) { + pts.resize(2); + pts[0].first = v[0] ; pts[0].second = v[1] ; + pts[1].first = v[0] + v[2]; pts[1].second = v[1] + v[3]; + } else {//polygon + if(DrawMode::Ellipse == mode) throw std::runtime_error("too many points for ellipse in ROI string"); + mode = DrawMode::Polygon; + pts.resize(v.size() / 2); + for(size_t i = 0; i < pts.size(); i++) { + pts[i].first = v[2*i]; pts[i].second = v[2*i+1]; + } + if(pts.front() != pts.back()) throw std::runtime_error("polygon not closed in ROI string"); + } + } + +}//emsphinx + + +#endif//_ROI_H_ diff --git a/include/miniz/LICENSE b/include/miniz/LICENSE new file mode 100755 index 0000000..1982f4b --- /dev/null +++ b/include/miniz/LICENSE @@ -0,0 +1,22 @@ +Copyright 2013-2014 RAD Game Tools and Valve Software +Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + +All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/include/miniz/miniz.c b/include/miniz/miniz.c new file mode 100755 index 0000000..0e11060 --- /dev/null +++ b/include/miniz/miniz.c @@ -0,0 +1,7564 @@ +/************************************************************************** + * + * Copyright 2013-2014 RAD Game Tools and Valve Software + * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + +#include "miniz.h" + +typedef unsigned char mz_validate_uint16[sizeof(mz_uint16) == 2 ? 1 : -1]; +typedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1]; +typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1]; + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- zlib-style API's */ + +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) +{ + mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); + size_t block_len = buf_len % 5552; + if (!ptr) + return MZ_ADLER32_INIT; + while (buf_len) + { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) + { + s1 += ptr[0], s2 += s1; + s1 += ptr[1], s2 += s1; + s1 += ptr[2], s2 += s1; + s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; + s1 += ptr[5], s2 += s1; + s1 += ptr[6], s2 += s1; + s1 += ptr[7], s2 += s1; + } + for (; i < block_len; ++i) + s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; + buf_len -= block_len; + block_len = 5552; + } + return (s2 << 16) + s1; +} + +/* Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ */ +#if 0 + mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) + { + static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; + mz_uint32 crcu32 = (mz_uint32)crc; + if (!ptr) + return MZ_CRC32_INIT; + crcu32 = ~crcu32; + while (buf_len--) + { + mz_uint8 b = *ptr++; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; + } + return ~crcu32; + } +#else +/* Faster, but larger CPU cache footprint. + */ +mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) +{ + static const mz_uint32 s_crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, + 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, + 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, + 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, + 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, + 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, + 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, + 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, + 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, + 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, + 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, + 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, + 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, + 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, + 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, + 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, + 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, + 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, + 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, + 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, + 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, + 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, + 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, + 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, + 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, + 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, + 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, + 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, + 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, + 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, + 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + mz_uint32 crc32 = (mz_uint32)crc ^ 0xFFFFFFFF; + const mz_uint8 *pByte_buf = (const mz_uint8 *)ptr; + + while (buf_len >= 4) + { + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[1]) & 0xFF]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[2]) & 0xFF]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[3]) & 0xFF]; + pByte_buf += 4; + buf_len -= 4; + } + + while (buf_len) + { + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; + ++pByte_buf; + --buf_len; + } + + return ~crc32; +} +#endif + +void mz_free(void *p) +{ + MZ_FREE(p); +} + +void *miniz_def_alloc_func(void *opaque, size_t items, size_t size) +{ + (void)opaque, (void)items, (void)size; + return MZ_MALLOC(items * size); +} +void miniz_def_free_func(void *opaque, void *address) +{ + (void)opaque, (void)address; + MZ_FREE(address); +} +void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size) +{ + (void)opaque, (void)address, (void)items, (void)size; + return MZ_REALLOC(address, items * size); +} + +const char *mz_version(void) +{ + return MZ_VERSION; +} + +#ifndef MINIZ_NO_ZLIB_APIS + +int mz_deflateInit(mz_streamp pStream, int level) +{ + return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY); +} + +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy) +{ + tdefl_compressor *pComp; + mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy); + + if (!pStream) + return MZ_STREAM_ERROR; + if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))) + return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = MZ_ADLER32_INIT; + pStream->msg = NULL; + pStream->reserved = 0; + pStream->total_in = 0; + pStream->total_out = 0; + if (!pStream->zalloc) + pStream->zalloc = miniz_def_alloc_func; + if (!pStream->zfree) + pStream->zfree = miniz_def_free_func; + + pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pComp; + + if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY) + { + mz_deflateEnd(pStream); + return MZ_PARAM_ERROR; + } + + return MZ_OK; +} + +int mz_deflateReset(mz_streamp pStream) +{ + if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree)) + return MZ_STREAM_ERROR; + pStream->total_in = pStream->total_out = 0; + tdefl_init((tdefl_compressor *)pStream->state, NULL, NULL, ((tdefl_compressor *)pStream->state)->m_flags); + return MZ_OK; +} + +int mz_deflate(mz_streamp pStream, int flush) +{ + size_t in_bytes, out_bytes; + mz_ulong orig_total_in, orig_total_out; + int mz_status = MZ_OK; + + if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out)) + return MZ_STREAM_ERROR; + if (!pStream->avail_out) + return MZ_BUF_ERROR; + + if (flush == MZ_PARTIAL_FLUSH) + flush = MZ_SYNC_FLUSH; + + if (((tdefl_compressor *)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE) + return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR; + + orig_total_in = pStream->total_in; + orig_total_out = pStream->total_out; + for (;;) + { + tdefl_status defl_status; + in_bytes = pStream->avail_in; + out_bytes = pStream->avail_out; + + defl_status = tdefl_compress((tdefl_compressor *)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush); + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tdefl_get_adler32((tdefl_compressor *)pStream->state); + + pStream->next_out += (mz_uint)out_bytes; + pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (defl_status < 0) + { + mz_status = MZ_STREAM_ERROR; + break; + } + else if (defl_status == TDEFL_STATUS_DONE) + { + mz_status = MZ_STREAM_END; + break; + } + else if (!pStream->avail_out) + break; + else if ((!pStream->avail_in) && (flush != MZ_FINISH)) + { + if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out)) + break; + return MZ_BUF_ERROR; /* Can't make forward progress without some input. + */ + } + } + return mz_status; +} + +int mz_deflateEnd(mz_streamp pStream) +{ + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) +{ + (void)pStream; + /* This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.) */ + return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5); +} + +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level) +{ + int status; + mz_stream stream; + memset(&stream, 0, sizeof(stream)); + + /* In case mz_ulong is 64-bits (argh I hate longs). */ + if ((source_len | *pDest_len) > 0xFFFFFFFFU) + return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_deflateInit(&stream, level); + if (status != MZ_OK) + return status; + + status = mz_deflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) + { + mz_deflateEnd(&stream); + return (status == MZ_OK) ? MZ_BUF_ERROR : status; + } + + *pDest_len = stream.total_out; + return mz_deflateEnd(&stream); +} + +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION); +} + +mz_ulong mz_compressBound(mz_ulong source_len) +{ + return mz_deflateBound(NULL, source_len); +} + +typedef struct +{ + tinfl_decompressor m_decomp; + mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; + int m_window_bits; + mz_uint8 m_dict[TINFL_LZ_DICT_SIZE]; + tinfl_status m_last_status; +} inflate_state; + +int mz_inflateInit2(mz_streamp pStream, int window_bits) +{ + inflate_state *pDecomp; + if (!pStream) + return MZ_STREAM_ERROR; + if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)) + return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = 0; + pStream->msg = NULL; + pStream->total_in = 0; + pStream->total_out = 0; + pStream->reserved = 0; + if (!pStream->zalloc) + pStream->zalloc = miniz_def_alloc_func; + if (!pStream->zfree) + pStream->zfree = miniz_def_free_func; + + pDecomp = (inflate_state *)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state)); + if (!pDecomp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pDecomp; + + tinfl_init(&pDecomp->m_decomp); + pDecomp->m_dict_ofs = 0; + pDecomp->m_dict_avail = 0; + pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; + pDecomp->m_first_call = 1; + pDecomp->m_has_flushed = 0; + pDecomp->m_window_bits = window_bits; + + return MZ_OK; +} + +int mz_inflateInit(mz_streamp pStream) +{ + return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS); +} + +int mz_inflate(mz_streamp pStream, int flush) +{ + inflate_state *pState; + mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32; + size_t in_bytes, out_bytes, orig_avail_in; + tinfl_status status; + + if ((!pStream) || (!pStream->state)) + return MZ_STREAM_ERROR; + if (flush == MZ_PARTIAL_FLUSH) + flush = MZ_SYNC_FLUSH; + if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) + return MZ_STREAM_ERROR; + + pState = (inflate_state *)pStream->state; + if (pState->m_window_bits > 0) + decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; + orig_avail_in = pStream->avail_in; + + first_call = pState->m_first_call; + pState->m_first_call = 0; + if (pState->m_last_status < 0) + return MZ_DATA_ERROR; + + if (pState->m_has_flushed && (flush != MZ_FINISH)) + return MZ_STREAM_ERROR; + pState->m_has_flushed |= (flush == MZ_FINISH); + + if ((flush == MZ_FINISH) && (first_call)) + { + /* MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. */ + decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; + in_bytes = pStream->avail_in; + out_bytes = pStream->avail_out; + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags); + pState->m_last_status = status; + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + pStream->next_out += (mz_uint)out_bytes; + pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (status < 0) + return MZ_DATA_ERROR; + else if (status != TINFL_STATUS_DONE) + { + pState->m_last_status = TINFL_STATUS_FAILED; + return MZ_BUF_ERROR; + } + return MZ_STREAM_END; + } + /* flush != MZ_FINISH then we must assume there's more input. */ + if (flush != MZ_FINISH) + decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; + + if (pState->m_dict_avail) + { + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; + pStream->avail_out -= n; + pStream->total_out += n; + pState->m_dict_avail -= n; + pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; + } + + for (;;) + { + in_bytes = pStream->avail_in; + out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs; + + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags); + pState->m_last_status = status; + + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + + pState->m_dict_avail = (mz_uint)out_bytes; + + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; + pStream->avail_out -= n; + pStream->total_out += n; + pState->m_dict_avail -= n; + pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + + if (status < 0) + return MZ_DATA_ERROR; /* Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). */ + else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in)) + return MZ_BUF_ERROR; /* Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. */ + else if (flush == MZ_FINISH) + { + /* The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. */ + if (status == TINFL_STATUS_DONE) + return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END; + /* status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. */ + else if (!pStream->avail_out) + return MZ_BUF_ERROR; + } + else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail)) + break; + } + + return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; +} + +int mz_inflateEnd(mz_streamp pStream) +{ + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + mz_stream stream; + int status; + memset(&stream, 0, sizeof(stream)); + + /* In case mz_ulong is 64-bits (argh I hate longs). */ + if ((source_len | *pDest_len) > 0xFFFFFFFFU) + return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_inflateInit(&stream); + if (status != MZ_OK) + return status; + + status = mz_inflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) + { + mz_inflateEnd(&stream); + return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status; + } + *pDest_len = stream.total_out; + + return mz_inflateEnd(&stream); +} + +const char *mz_error(int err) +{ + static struct + { + int m_err; + const char *m_pDesc; + } s_error_descs[] = + { + { MZ_OK, "" }, { MZ_STREAM_END, "stream end" }, { MZ_NEED_DICT, "need dictionary" }, { MZ_ERRNO, "file error" }, { MZ_STREAM_ERROR, "stream error" }, { MZ_DATA_ERROR, "data error" }, { MZ_MEM_ERROR, "out of memory" }, { MZ_BUF_ERROR, "buf error" }, { MZ_VERSION_ERROR, "version error" }, { MZ_PARAM_ERROR, "parameter error" } + }; + mz_uint i; + for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) + if (s_error_descs[i].m_err == err) + return s_error_descs[i].m_pDesc; + return NULL; +} + +#endif /*MINIZ_NO_ZLIB_APIS */ + +#ifdef __cplusplus +} +#endif + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ +/************************************************************************** + * + * Copyright 2013-2014 RAD Game Tools and Valve Software + * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + + + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- Low-level Compression (independent from all decompression API's) */ + +/* Purposely making these tables static for faster init and thread safety. */ +static const mz_uint16 s_tdefl_len_sym[256] = + { + 257, 258, 259, 260, 261, 262, 263, 264, 265, 265, 266, 266, 267, 267, 268, 268, 269, 269, 269, 269, 270, 270, 270, 270, 271, 271, 271, 271, 272, 272, 272, 272, + 273, 273, 273, 273, 273, 273, 273, 273, 274, 274, 274, 274, 274, 274, 274, 274, 275, 275, 275, 275, 275, 275, 275, 275, 276, 276, 276, 276, 276, 276, 276, 276, + 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, + 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, + 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, + 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, + 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, + 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 285 + }; + +static const mz_uint8 s_tdefl_len_extra[256] = + { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0 + }; + +static const mz_uint8 s_tdefl_small_dist_sym[512] = + { + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17 + }; + +static const mz_uint8 s_tdefl_small_dist_extra[512] = + { + 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7 + }; + +static const mz_uint8 s_tdefl_large_dist_sym[128] = + { + 0, 0, 18, 19, 20, 20, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 + }; + +static const mz_uint8 s_tdefl_large_dist_extra[128] = + { + 0, 0, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13 + }; + +/* Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values. */ +typedef struct +{ + mz_uint16 m_key, m_sym_index; +} tdefl_sym_freq; +static tdefl_sym_freq *tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq *pSyms0, tdefl_sym_freq *pSyms1) +{ + mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; + tdefl_sym_freq *pCur_syms = pSyms0, *pNew_syms = pSyms1; + MZ_CLEAR_OBJ(hist); + for (i = 0; i < num_syms; i++) + { + mz_uint freq = pSyms0[i].m_key; + hist[freq & 0xFF]++; + hist[256 + ((freq >> 8) & 0xFF)]++; + } + while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) + total_passes--; + for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) + { + const mz_uint32 *pHist = &hist[pass << 8]; + mz_uint offsets[256], cur_ofs = 0; + for (i = 0; i < 256; i++) + { + offsets[i] = cur_ofs; + cur_ofs += pHist[i]; + } + for (i = 0; i < num_syms; i++) + pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i]; + { + tdefl_sym_freq *t = pCur_syms; + pCur_syms = pNew_syms; + pNew_syms = t; + } + } + return pCur_syms; +} + +/* tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. */ +static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n) +{ + int root, leaf, next, avbl, used, dpth; + if (n == 0) + return; + else if (n == 1) + { + A[0].m_key = 1; + return; + } + A[0].m_key += A[1].m_key; + root = 0; + leaf = 2; + for (next = 1; next < n - 1; next++) + { + if (leaf >= n || A[root].m_key < A[leaf].m_key) + { + A[next].m_key = A[root].m_key; + A[root++].m_key = (mz_uint16)next; + } + else + A[next].m_key = A[leaf++].m_key; + if (leaf >= n || (root < next && A[root].m_key < A[leaf].m_key)) + { + A[next].m_key = (mz_uint16)(A[next].m_key + A[root].m_key); + A[root++].m_key = (mz_uint16)next; + } + else + A[next].m_key = (mz_uint16)(A[next].m_key + A[leaf++].m_key); + } + A[n - 2].m_key = 0; + for (next = n - 3; next >= 0; next--) + A[next].m_key = A[A[next].m_key].m_key + 1; + avbl = 1; + used = dpth = 0; + root = n - 2; + next = n - 1; + while (avbl > 0) + { + while (root >= 0 && (int)A[root].m_key == dpth) + { + used++; + root--; + } + while (avbl > used) + { + A[next--].m_key = (mz_uint16)(dpth); + avbl--; + } + avbl = 2 * used; + dpth++; + used = 0; + } +} + +/* Limits canonical Huffman code table's max code size. */ +enum +{ + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 +}; +static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size) +{ + int i; + mz_uint32 total = 0; + if (code_list_len <= 1) + return; + for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) + pNum_codes[max_code_size] += pNum_codes[i]; + for (i = max_code_size; i > 0; i--) + total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i)); + while (total != (1UL << max_code_size)) + { + pNum_codes[max_code_size]--; + for (i = max_code_size - 1; i > 0; i--) + if (pNum_codes[i]) + { + pNum_codes[i]--; + pNum_codes[i + 1] += 2; + break; + } + total--; + } +} + +static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table) +{ + int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; + mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; + MZ_CLEAR_OBJ(num_codes); + if (static_table) + { + for (i = 0; i < table_len; i++) + num_codes[d->m_huff_code_sizes[table_num][i]]++; + } + else + { + tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms; + int num_used_syms = 0; + const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0]; + for (i = 0; i < table_len; i++) + if (pSym_count[i]) + { + syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; + syms0[num_used_syms++].m_sym_index = (mz_uint16)i; + } + + pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); + tdefl_calculate_minimum_redundancy(pSyms, num_used_syms); + + for (i = 0; i < num_used_syms; i++) + num_codes[pSyms[i].m_key]++; + + tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit); + + MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); + MZ_CLEAR_OBJ(d->m_huff_codes[table_num]); + for (i = 1, j = num_used_syms; i <= code_size_limit; i++) + for (l = num_codes[i]; l > 0; l--) + d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); + } + + next_code[1] = 0; + for (j = 0, i = 2; i <= code_size_limit; i++) + next_code[i] = j = ((j + num_codes[i - 1]) << 1); + + for (i = 0; i < table_len; i++) + { + mz_uint rev_code = 0, code, code_size; + if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) + continue; + code = next_code[code_size]++; + for (l = code_size; l > 0; l--, code >>= 1) + rev_code = (rev_code << 1) | (code & 1); + d->m_huff_codes[table_num][i] = (mz_uint16)rev_code; + } +} + +#define TDEFL_PUT_BITS(b, l) \ + do \ + { \ + mz_uint bits = b; \ + mz_uint len = l; \ + MZ_ASSERT(bits <= ((1U << len) - 1U)); \ + d->m_bit_buffer |= (bits << d->m_bits_in); \ + d->m_bits_in += len; \ + while (d->m_bits_in >= 8) \ + { \ + if (d->m_pOutput_buf < d->m_pOutput_buf_end) \ + *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \ + d->m_bit_buffer >>= 8; \ + d->m_bits_in -= 8; \ + } \ + } \ + MZ_MACRO_END + +#define TDEFL_RLE_PREV_CODE_SIZE() \ + { \ + if (rle_repeat_count) \ + { \ + if (rle_repeat_count < 3) \ + { \ + d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \ + while (rle_repeat_count--) \ + packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \ + } \ + else \ + { \ + d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 16; \ + packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3); \ + } \ + rle_repeat_count = 0; \ + } \ + } + +#define TDEFL_RLE_ZERO_CODE_SIZE() \ + { \ + if (rle_z_count) \ + { \ + if (rle_z_count < 3) \ + { \ + d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); \ + while (rle_z_count--) \ + packed_code_sizes[num_packed_code_sizes++] = 0; \ + } \ + else if (rle_z_count <= 10) \ + { \ + d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 17; \ + packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3); \ + } \ + else \ + { \ + d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 18; \ + packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \ + } \ + rle_z_count = 0; \ + } \ + } + +static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + +static void tdefl_start_dynamic_block(tdefl_compressor *d) +{ + int num_lit_codes, num_dist_codes, num_bit_lengths; + mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index; + mz_uint8 code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF; + + d->m_huff_count[0][256] = 1; + + tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE); + tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE); + + for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) + if (d->m_huff_code_sizes[0][num_lit_codes - 1]) + break; + for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) + if (d->m_huff_code_sizes[1][num_dist_codes - 1]) + break; + + memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes); + memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes); + total_code_sizes_to_pack = num_lit_codes + num_dist_codes; + num_packed_code_sizes = 0; + rle_z_count = 0; + rle_repeat_count = 0; + + memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2); + for (i = 0; i < total_code_sizes_to_pack; i++) + { + mz_uint8 code_size = code_sizes_to_pack[i]; + if (!code_size) + { + TDEFL_RLE_PREV_CODE_SIZE(); + if (++rle_z_count == 138) + { + TDEFL_RLE_ZERO_CODE_SIZE(); + } + } + else + { + TDEFL_RLE_ZERO_CODE_SIZE(); + if (code_size != prev_code_size) + { + TDEFL_RLE_PREV_CODE_SIZE(); + d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1); + packed_code_sizes[num_packed_code_sizes++] = code_size; + } + else if (++rle_repeat_count == 6) + { + TDEFL_RLE_PREV_CODE_SIZE(); + } + } + prev_code_size = code_size; + } + if (rle_repeat_count) + { + TDEFL_RLE_PREV_CODE_SIZE(); + } + else + { + TDEFL_RLE_ZERO_CODE_SIZE(); + } + + tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE); + + TDEFL_PUT_BITS(2, 2); + + TDEFL_PUT_BITS(num_lit_codes - 257, 5); + TDEFL_PUT_BITS(num_dist_codes - 1, 5); + + for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) + if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) + break; + num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); + TDEFL_PUT_BITS(num_bit_lengths - 4, 4); + for (i = 0; (int)i < num_bit_lengths; i++) + TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3); + + for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes;) + { + mz_uint code = packed_code_sizes[packed_code_sizes_index++]; + MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2); + TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]); + if (code >= 16) + TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]); + } +} + +static void tdefl_start_static_block(tdefl_compressor *d) +{ + mz_uint i; + mz_uint8 *p = &d->m_huff_code_sizes[0][0]; + + for (i = 0; i <= 143; ++i) + *p++ = 8; + for (; i <= 255; ++i) + *p++ = 9; + for (; i <= 279; ++i) + *p++ = 7; + for (; i <= 287; ++i) + *p++ = 8; + + memset(d->m_huff_code_sizes[1], 5, 32); + + tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE); + tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE); + + TDEFL_PUT_BITS(1, 2); +} + +static const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) +{ + mz_uint flags; + mz_uint8 *pLZ_codes; + mz_uint8 *pOutput_buf = d->m_pOutput_buf; + mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf; + mz_uint64 bit_buffer = d->m_bit_buffer; + mz_uint bits_in = d->m_bits_in; + +#define TDEFL_PUT_BITS_FAST(b, l) \ + { \ + bit_buffer |= (((mz_uint64)(b)) << bits_in); \ + bits_in += (l); \ + } + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1) + { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + + if (flags & 1) + { + mz_uint s0, s1, n0, n1, sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], match_dist = *(const mz_uint16 *)(pLZ_codes + 1); + pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); + + /* This sequence coaxes MSVC into using cmov's vs. jmp's. */ + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + n0 = s_tdefl_small_dist_extra[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[match_dist >> 8]; + n1 = s_tdefl_large_dist_extra[match_dist >> 8]; + sym = (match_dist < 512) ? s0 : s1; + num_extra_bits = (match_dist < 512) ? n0 : n1; + + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } + else + { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) + { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) + { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + } + + if (pOutput_buf >= d->m_pOutput_buf_end) + return MZ_FALSE; + + *(mz_uint64 *)pOutput_buf = bit_buffer; + pOutput_buf += (bits_in >> 3); + bit_buffer >>= (bits_in & ~7); + bits_in &= 7; + } + +#undef TDEFL_PUT_BITS_FAST + + d->m_pOutput_buf = pOutput_buf; + d->m_bits_in = 0; + d->m_bit_buffer = 0; + + while (bits_in) + { + mz_uint32 n = MZ_MIN(bits_in, 16); + TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n); + bit_buffer >>= n; + bits_in -= n; + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#else +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) +{ + mz_uint flags; + mz_uint8 *pLZ_codes; + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1) + { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + if (flags & 1) + { + mz_uint sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); + pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); + + if (match_dist < 512) + { + sym = s_tdefl_small_dist_sym[match_dist]; + num_extra_bits = s_tdefl_small_dist_extra[match_dist]; + } + else + { + sym = s_tdefl_large_dist_sym[match_dist >> 8]; + num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8]; + } + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } + else + { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS */ + +static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block) +{ + if (static_block) + tdefl_start_static_block(d); + else + tdefl_start_dynamic_block(d); + return tdefl_compress_lz_codes(d); +} + +static int tdefl_flush_block(tdefl_compressor *d, int flush) +{ + mz_uint saved_bit_buf, saved_bits_in; + mz_uint8 *pSaved_output_buf; + mz_bool comp_block_succeeded = MZ_FALSE; + int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size; + mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) : d->m_output_buf; + + d->m_pOutput_buf = pOutput_buf_start; + d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16; + + MZ_ASSERT(!d->m_output_flush_remaining); + d->m_output_flush_ofs = 0; + d->m_output_flush_remaining = 0; + + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left); + d->m_pLZ_code_buf -= (d->m_num_flags_left == 8); + + if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) + { + TDEFL_PUT_BITS(0x78, 8); + TDEFL_PUT_BITS(0x01, 8); + } + + TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); + + pSaved_output_buf = d->m_pOutput_buf; + saved_bit_buf = d->m_bit_buffer; + saved_bits_in = d->m_bits_in; + + if (!use_raw_block) + comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48)); + + /* If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. */ + if (((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) && + ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size)) + { + mz_uint i; + d->m_pOutput_buf = pSaved_output_buf; + d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + TDEFL_PUT_BITS(0, 2); + if (d->m_bits_in) + { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF) + { + TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16); + } + for (i = 0; i < d->m_total_lz_bytes; ++i) + { + TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8); + } + } + /* Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. */ + else if (!comp_block_succeeded) + { + d->m_pOutput_buf = pSaved_output_buf; + d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + tdefl_compress_block(d, MZ_TRUE); + } + + if (flush) + { + if (flush == TDEFL_FINISH) + { + if (d->m_bits_in) + { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) + { + mz_uint i, a = d->m_adler32; + for (i = 0; i < 4; i++) + { + TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); + a <<= 8; + } + } + } + else + { + mz_uint i, z = 0; + TDEFL_PUT_BITS(0, 3); + if (d->m_bits_in) + { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + for (i = 2; i; --i, z ^= 0xFFFF) + { + TDEFL_PUT_BITS(z & 0xFFFF, 16); + } + } + } + + MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end); + + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; + d->m_pLZ_flags = d->m_lz_code_buf; + d->m_num_flags_left = 8; + d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; + d->m_total_lz_bytes = 0; + d->m_block_index++; + + if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0) + { + if (d->m_pPut_buf_func) + { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user)) + return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED); + } + else if (pOutput_buf_start == d->m_output_buf) + { + int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs)); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy); + d->m_out_buf_ofs += bytes_to_copy; + if ((n -= bytes_to_copy) != 0) + { + d->m_output_flush_ofs = bytes_to_copy; + d->m_output_flush_remaining = n; + } + } + else + { + d->m_out_buf_ofs += n; + } + } + + return d->m_output_flush_remaining; +} + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES +#ifdef MINIZ_UNALIGNED_USE_MEMCPY +static inline mz_uint16 TDEFL_READ_UNALIGNED_WORD(const mz_uint8* p) +{ + mz_uint16 ret; + memcpy(&ret, p, sizeof(mz_uint16)); + return ret; +} +static inline mz_uint16 TDEFL_READ_UNALIGNED_WORD2(const mz_uint16* p) +{ + mz_uint16 ret; + memcpy(&ret, p, sizeof(mz_uint16)); + return ret; +} +#else +#define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16 *)(p) +#define TDEFL_READ_UNALIGNED_WORD2(p) *(const mz_uint16 *)(p) +#endif +static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) +{ + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint16 *s = (const mz_uint16 *)(d->m_dict + pos), *p, *q; + mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD2(s); + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); + if (max_match_len <= match_len) + return; + for (;;) + { + for (;;) + { + if (--num_probes_left == 0) + return; +#define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ + return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) \ + break; + TDEFL_PROBE; + TDEFL_PROBE; + TDEFL_PROBE; + } + if (!dist) + break; + q = (const mz_uint16 *)(d->m_dict + probe_pos); + if (TDEFL_READ_UNALIGNED_WORD2(q) != s01) + continue; + p = s; + probe_len = 32; + do + { + } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && + (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0)); + if (!probe_len) + { + *pMatch_dist = dist; + *pMatch_len = MZ_MIN(max_match_len, (mz_uint)TDEFL_MAX_MATCH_LEN); + break; + } + else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q)) > match_len) + { + *pMatch_dist = dist; + if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len) + break; + c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]); + } + } +} +#else +static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) +{ + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint8 *s = d->m_dict + pos, *p, *q; + mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1]; + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); + if (max_match_len <= match_len) + return; + for (;;) + { + for (;;) + { + if (--num_probes_left == 0) + return; +#define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ + return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) \ + break; + TDEFL_PROBE; + TDEFL_PROBE; + TDEFL_PROBE; + } + if (!dist) + break; + p = s; + q = d->m_dict + probe_pos; + for (probe_len = 0; probe_len < max_match_len; probe_len++) + if (*p++ != *q++) + break; + if (probe_len > match_len) + { + *pMatch_dist = dist; + if ((*pMatch_len = match_len = probe_len) == max_match_len) + return; + c0 = d->m_dict[pos + match_len]; + c1 = d->m_dict[pos + match_len - 1]; + } + } +} +#endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES */ + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN +static mz_bool tdefl_compress_fast(tdefl_compressor *d) +{ + /* Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. */ + mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, num_flags_left = d->m_num_flags_left; + mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags; + mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + + while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size))) + { + const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096; + mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size); + d->m_src_buf_left -= num_bytes_to_process; + lookahead_size += num_bytes_to_process; + + while (num_bytes_to_process) + { + mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process); + memcpy(d->m_dict + dst_pos, d->m_pSrc, n); + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos)); + d->m_pSrc += n; + dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK; + num_bytes_to_process -= n; + } + + dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size); + if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) + break; + + while (lookahead_size >= 4) + { + mz_uint cur_match_dist, cur_match_len = 1; + mz_uint8 *pCur_dict = d->m_dict + cur_pos; + mz_uint first_trigram = (*(const mz_uint32 *)pCur_dict) & 0xFFFFFF; + mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK; + mz_uint probe_pos = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)lookahead_pos; + + if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && ((*(const mz_uint32 *)(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & 0xFFFFFF) == first_trigram)) + { + const mz_uint16 *p = (const mz_uint16 *)pCur_dict; + const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos); + mz_uint32 probe_len = 32; + do + { + } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && + (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0)); + cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q); + if (!probe_len) + cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0; + + if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U))) + { + cur_match_len = 1; + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + else + { + mz_uint32 s0, s1; + cur_match_len = MZ_MIN(cur_match_len, lookahead_size); + + MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE)); + + cur_match_dist--; + + pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN); + *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist; + pLZ_code_buf += 3; + *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80); + + s0 = s_tdefl_small_dist_sym[cur_match_dist & 511]; + s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8]; + d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++; + + d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++; + } + } + else + { + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + + if (--num_flags_left == 0) + { + num_flags_left = 8; + pLZ_flags = pLZ_code_buf++; + } + + total_lz_bytes += cur_match_len; + lookahead_pos += cur_match_len; + dict_size = MZ_MIN(dict_size + cur_match_len, (mz_uint)TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK; + MZ_ASSERT(lookahead_size >= cur_match_len); + lookahead_size -= cur_match_len; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) + { + int n; + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; + pLZ_code_buf = d->m_pLZ_code_buf; + pLZ_flags = d->m_pLZ_flags; + num_flags_left = d->m_num_flags_left; + } + } + + while (lookahead_size) + { + mz_uint8 lit = d->m_dict[cur_pos]; + + total_lz_bytes++; + *pLZ_code_buf++ = lit; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + if (--num_flags_left == 0) + { + num_flags_left = 8; + pLZ_flags = pLZ_code_buf++; + } + + d->m_huff_count[0][lit]++; + + lookahead_pos++; + dict_size = MZ_MIN(dict_size + 1, (mz_uint)TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + lookahead_size--; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) + { + int n; + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; + pLZ_code_buf = d->m_pLZ_code_buf; + pLZ_flags = d->m_pLZ_flags; + num_flags_left = d->m_num_flags_left; + } + } + } + + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + return MZ_TRUE; +} +#endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */ + +static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit) +{ + d->m_total_lz_bytes++; + *d->m_pLZ_code_buf++ = lit; + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); + if (--d->m_num_flags_left == 0) + { + d->m_num_flags_left = 8; + d->m_pLZ_flags = d->m_pLZ_code_buf++; + } + d->m_huff_count[0][lit]++; +} + +static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist) +{ + mz_uint32 s0, s1; + + MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE)); + + d->m_total_lz_bytes += match_len; + + d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN); + + match_dist -= 1; + d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF); + d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); + d->m_pLZ_code_buf += 3; + + *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); + if (--d->m_num_flags_left == 0) + { + d->m_num_flags_left = 8; + d->m_pLZ_flags = d->m_pLZ_code_buf++; + } + + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; + d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; + + if (match_len >= TDEFL_MIN_MATCH_LEN) + d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; +} + +static mz_bool tdefl_compress_normal(tdefl_compressor *d) +{ + const mz_uint8 *pSrc = d->m_pSrc; + size_t src_buf_left = d->m_src_buf_left; + tdefl_flush flush = d->m_flush; + + while ((src_buf_left) || ((flush) && (d->m_lookahead_size))) + { + mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos; + /* Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN. */ + if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1)) + { + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2; + mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK]; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); + const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process; + src_buf_left -= num_bytes_to_process; + d->m_lookahead_size += num_bytes_to_process; + while (pSrc != pSrc_end) + { + mz_uint8 c = *pSrc++; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)(ins_pos); + dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + ins_pos++; + } + } + else + { + while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + { + mz_uint8 c = *pSrc++; + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + src_buf_left--; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN) + { + mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2; + mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << (TDEFL_LZ_HASH_SHIFT * 2)) ^ (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)(ins_pos); + } + } + } + d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size); + if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + break; + + /* Simple lazy/greedy parsing state machine. */ + len_to_move = 1; + cur_match_dist = 0; + cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); + cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS)) + { + if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) + { + mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK]; + cur_match_len = 0; + while (cur_match_len < d->m_lookahead_size) + { + if (d->m_dict[cur_pos + cur_match_len] != c) + break; + cur_match_len++; + } + if (cur_match_len < TDEFL_MIN_MATCH_LEN) + cur_match_len = 0; + else + cur_match_dist = 1; + } + } + else + { + tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len); + } + if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U)) || (cur_pos == cur_match_dist) || ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5))) + { + cur_match_dist = cur_match_len = 0; + } + if (d->m_saved_match_len) + { + if (cur_match_len > d->m_saved_match_len) + { + tdefl_record_literal(d, (mz_uint8)d->m_saved_lit); + if (cur_match_len >= 128) + { + tdefl_record_match(d, cur_match_len, cur_match_dist); + d->m_saved_match_len = 0; + len_to_move = cur_match_len; + } + else + { + d->m_saved_lit = d->m_dict[cur_pos]; + d->m_saved_match_dist = cur_match_dist; + d->m_saved_match_len = cur_match_len; + } + } + else + { + tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist); + len_to_move = d->m_saved_match_len - 1; + d->m_saved_match_len = 0; + } + } + else if (!cur_match_dist) + tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]); + else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128)) + { + tdefl_record_match(d, cur_match_len, cur_match_dist); + len_to_move = cur_match_len; + } + else + { + d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; + d->m_saved_match_dist = cur_match_dist; + d->m_saved_match_len = cur_match_len; + } + /* Move the lookahead forward by len_to_move bytes. */ + d->m_lookahead_pos += len_to_move; + MZ_ASSERT(d->m_lookahead_size >= len_to_move); + d->m_lookahead_size -= len_to_move; + d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, (mz_uint)TDEFL_LZ_DICT_SIZE); + /* Check if it's time to flush the current LZ codes to the internal output buffer. */ + if ((d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) || + ((d->m_total_lz_bytes > 31 * 1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS)))) + { + int n; + d->m_pSrc = pSrc; + d->m_src_buf_left = src_buf_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + } + } + + d->m_pSrc = pSrc; + d->m_src_buf_left = src_buf_left; + return MZ_TRUE; +} + +static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d) +{ + if (d->m_pIn_buf_size) + { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + } + + if (d->m_pOut_buf_size) + { + size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n); + d->m_output_flush_ofs += (mz_uint)n; + d->m_output_flush_remaining -= (mz_uint)n; + d->m_out_buf_ofs += n; + + *d->m_pOut_buf_size = d->m_out_buf_ofs; + } + + return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush) +{ + if (!d) + { + if (pIn_buf_size) + *pIn_buf_size = 0; + if (pOut_buf_size) + *pOut_buf_size = 0; + return TDEFL_STATUS_BAD_PARAM; + } + + d->m_pIn_buf = pIn_buf; + d->m_pIn_buf_size = pIn_buf_size; + d->m_pOut_buf = pOut_buf; + d->m_pOut_buf_size = pOut_buf_size; + d->m_pSrc = (const mz_uint8 *)(pIn_buf); + d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0; + d->m_out_buf_ofs = 0; + d->m_flush = flush; + + if (((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) || + (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf)) + { + if (pIn_buf_size) + *pIn_buf_size = 0; + if (pOut_buf_size) + *pOut_buf_size = 0; + return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM); + } + d->m_wants_to_finish |= (flush == TDEFL_FINISH); + + if ((d->m_output_flush_remaining) || (d->m_finished)) + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) && + ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) && + ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0)) + { + if (!tdefl_compress_fast(d)) + return d->m_prev_return_status; + } + else +#endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */ + { + if (!tdefl_compress_normal(d)) + return d->m_prev_return_status; + } + + if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf)) + d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf); + + if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining)) + { + if (tdefl_flush_block(d, flush) < 0) + return d->m_prev_return_status; + d->m_finished = (flush == TDEFL_FINISH); + if (flush == TDEFL_FULL_FLUSH) + { + MZ_CLEAR_OBJ(d->m_hash); + MZ_CLEAR_OBJ(d->m_next); + d->m_dict_size = 0; + } + } + + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); +} + +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush) +{ + MZ_ASSERT(d->m_pPut_buf_func); + return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush); +} + +tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + d->m_pPut_buf_func = pPut_buf_func; + d->m_pPut_buf_user = pPut_buf_user; + d->m_flags = (mz_uint)(flags); + d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; + d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; + d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; + if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) + MZ_CLEAR_OBJ(d->m_hash); + d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0; + d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0; + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; + d->m_pLZ_flags = d->m_lz_code_buf; + d->m_num_flags_left = 8; + d->m_pOutput_buf = d->m_output_buf; + d->m_pOutput_buf_end = d->m_output_buf; + d->m_prev_return_status = TDEFL_STATUS_OKAY; + d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; + d->m_adler32 = 1; + d->m_pIn_buf = NULL; + d->m_pOut_buf = NULL; + d->m_pIn_buf_size = NULL; + d->m_pOut_buf_size = NULL; + d->m_flush = TDEFL_NO_FLUSH; + d->m_pSrc = NULL; + d->m_src_buf_left = 0; + d->m_out_buf_ofs = 0; + if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) + MZ_CLEAR_OBJ(d->m_dict); + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + return TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d) +{ + return d->m_prev_return_status; +} + +mz_uint32 tdefl_get_adler32(tdefl_compressor *d) +{ + return d->m_adler32; +} + +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + tdefl_compressor *pComp; + mz_bool succeeded; + if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) + return MZ_FALSE; + pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); + if (!pComp) + return MZ_FALSE; + succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY); + succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE); + MZ_FREE(pComp); + return succeeded; +} + +typedef struct +{ + size_t m_size, m_capacity; + mz_uint8 *m_pBuf; + mz_bool m_expandable; +} tdefl_output_buffer; + +static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser) +{ + tdefl_output_buffer *p = (tdefl_output_buffer *)pUser; + size_t new_size = p->m_size + len; + if (new_size > p->m_capacity) + { + size_t new_capacity = p->m_capacity; + mz_uint8 *pNew_buf; + if (!p->m_expandable) + return MZ_FALSE; + do + { + new_capacity = MZ_MAX(128U, new_capacity << 1U); + } while (new_size > new_capacity); + pNew_buf = (mz_uint8 *)MZ_REALLOC(p->m_pBuf, new_capacity); + if (!pNew_buf) + return MZ_FALSE; + p->m_pBuf = pNew_buf; + p->m_capacity = new_capacity; + } + memcpy((mz_uint8 *)p->m_pBuf + p->m_size, pBuf, len); + p->m_size = new_size; + return MZ_TRUE; +} + +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + tdefl_output_buffer out_buf; + MZ_CLEAR_OBJ(out_buf); + if (!pOut_len) + return MZ_FALSE; + else + *pOut_len = 0; + out_buf.m_expandable = MZ_TRUE; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) + return NULL; + *pOut_len = out_buf.m_size; + return out_buf.m_pBuf; +} + +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) +{ + tdefl_output_buffer out_buf; + MZ_CLEAR_OBJ(out_buf); + if (!pOut_buf) + return 0; + out_buf.m_pBuf = (mz_uint8 *)pOut_buf; + out_buf.m_capacity = out_buf_len; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) + return 0; + return out_buf.m_size; +} + +static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + +/* level may actually range from [0,10] (10 is a "hidden" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files). */ +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy) +{ + mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); + if (window_bits > 0) + comp_flags |= TDEFL_WRITE_ZLIB_HEADER; + + if (!level) + comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; + else if (strategy == MZ_FILTERED) + comp_flags |= TDEFL_FILTER_MATCHES; + else if (strategy == MZ_HUFFMAN_ONLY) + comp_flags &= ~TDEFL_MAX_PROBES_MASK; + else if (strategy == MZ_FIXED) + comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; + else if (strategy == MZ_RLE) + comp_flags |= TDEFL_RLE_MATCHES; + + return comp_flags; +} + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4204) /* nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) */ +#endif + +/* Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at + http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/. + This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. */ +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip) +{ + /* Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. */ + static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); + tdefl_output_buffer out_buf; + int i, bpl = w * num_chans, y, z; + mz_uint32 c; + *pLen_out = 0; + if (!pComp) + return NULL; + MZ_CLEAR_OBJ(out_buf); + out_buf.m_expandable = MZ_TRUE; + out_buf.m_capacity = 57 + MZ_MAX(64, (1 + bpl) * h); + if (NULL == (out_buf.m_pBuf = (mz_uint8 *)MZ_MALLOC(out_buf.m_capacity))) + { + MZ_FREE(pComp); + return NULL; + } + /* write dummy header */ + for (z = 41; z; --z) + tdefl_output_buffer_putter(&z, 1, &out_buf); + /* compress image data */ + tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER); + for (y = 0; y < h; ++y) + { + tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); + tdefl_compress_buffer(pComp, (mz_uint8 *)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH); + } + if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) + { + MZ_FREE(pComp); + MZ_FREE(out_buf.m_pBuf); + return NULL; + } + /* write real header */ + *pLen_out = out_buf.m_size - 41; + { + static const mz_uint8 chans[] = { 0x00, 0x00, 0x04, 0x02, 0x06 }; + mz_uint8 pnghdr[41] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, + 0x0a, 0x1a, 0x0a, 0x00, 0x00, + 0x00, 0x0d, 0x49, 0x48, 0x44, + 0x52, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x49, 0x44, 0x41, + 0x54 }; + pnghdr[18] = (mz_uint8)(w >> 8); + pnghdr[19] = (mz_uint8)w; + pnghdr[22] = (mz_uint8)(h >> 8); + pnghdr[23] = (mz_uint8)h; + pnghdr[25] = chans[num_chans]; + pnghdr[33] = (mz_uint8)(*pLen_out >> 24); + pnghdr[34] = (mz_uint8)(*pLen_out >> 16); + pnghdr[35] = (mz_uint8)(*pLen_out >> 8); + pnghdr[36] = (mz_uint8)*pLen_out; + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, pnghdr + 12, 17); + for (i = 0; i < 4; ++i, c <<= 8) + ((mz_uint8 *)(pnghdr + 29))[i] = (mz_uint8)(c >> 24); + memcpy(out_buf.m_pBuf, pnghdr, 41); + } + /* write footer (IDAT CRC-32, followed by IEND chunk) */ + if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) + { + *pLen_out = 0; + MZ_FREE(pComp); + MZ_FREE(out_buf.m_pBuf); + return NULL; + } + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, out_buf.m_pBuf + 41 - 4, *pLen_out + 4); + for (i = 0; i < 4; ++i, c <<= 8) + (out_buf.m_pBuf + out_buf.m_size - 16)[i] = (mz_uint8)(c >> 24); + /* compute final size of file, grab compressed data buffer and return */ + *pLen_out += 57; + MZ_FREE(pComp); + return out_buf.m_pBuf; +} +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out) +{ + /* Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out) */ + return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE); +} + +/* Allocate the tdefl_compressor and tinfl_decompressor structures in C so that */ +/* non-C language bindings to tdefL_ and tinfl_ API don't need to worry about */ +/* structure size and allocation mechanism. */ +tdefl_compressor *tdefl_compressor_alloc() +{ + return (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); +} + +void tdefl_compressor_free(tdefl_compressor *pComp) +{ + MZ_FREE(pComp); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#ifdef __cplusplus +} +#endif +/************************************************************************** + * + * Copyright 2013-2014 RAD Game Tools and Valve Software + * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- Low-level Decompression (completely independent from all compression API's) */ + +#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) +#define TINFL_MEMSET(p, c, l) memset(p, c, l) + +#define TINFL_CR_BEGIN \ + switch (r->m_state) \ + { \ + case 0: +#define TINFL_CR_RETURN(state_index, result) \ + do \ + { \ + status = result; \ + r->m_state = state_index; \ + goto common_exit; \ + case state_index:; \ + } \ + MZ_MACRO_END +#define TINFL_CR_RETURN_FOREVER(state_index, result) \ + do \ + { \ + for (;;) \ + { \ + TINFL_CR_RETURN(state_index, result); \ + } \ + } \ + MZ_MACRO_END +#define TINFL_CR_FINISH } + +#define TINFL_GET_BYTE(state_index, c) \ + do \ + { \ + while (pIn_buf_cur >= pIn_buf_end) \ + { \ + TINFL_CR_RETURN(state_index, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); \ + } \ + c = *pIn_buf_cur++; \ + } \ + MZ_MACRO_END + +#define TINFL_NEED_BITS(state_index, n) \ + do \ + { \ + mz_uint c; \ + TINFL_GET_BYTE(state_index, c); \ + bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ + num_bits += 8; \ + } while (num_bits < (mz_uint)(n)) +#define TINFL_SKIP_BITS(state_index, n) \ + do \ + { \ + if (num_bits < (mz_uint)(n)) \ + { \ + TINFL_NEED_BITS(state_index, n); \ + } \ + bit_buf >>= (n); \ + num_bits -= (n); \ + } \ + MZ_MACRO_END +#define TINFL_GET_BITS(state_index, b, n) \ + do \ + { \ + if (num_bits < (mz_uint)(n)) \ + { \ + TINFL_NEED_BITS(state_index, n); \ + } \ + b = bit_buf & ((1 << (n)) - 1); \ + bit_buf >>= (n); \ + num_bits -= (n); \ + } \ + MZ_MACRO_END + +/* TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. */ +/* It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a */ +/* Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the */ +/* bit buffer contains >=15 bits (deflate's max. Huffman code size). */ +#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ + do \ + { \ + temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ + if (temp >= 0) \ + { \ + code_len = temp >> 9; \ + if ((code_len) && (num_bits >= code_len)) \ + break; \ + } \ + else if (num_bits > TINFL_FAST_LOOKUP_BITS) \ + { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do \ + { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while ((temp < 0) && (num_bits >= (code_len + 1))); \ + if (temp >= 0) \ + break; \ + } \ + TINFL_GET_BYTE(state_index, c); \ + bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ + num_bits += 8; \ + } while (num_bits < 15); + +/* TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read */ +/* beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully */ +/* decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. */ +/* The slow path is only executed at the very end of the input buffer. */ +/* v1.16: The original macro handled the case at the very end of the passed-in input buffer, but we also need to handle the case where the user passes in 1+zillion bytes */ +/* following the deflate data and our non-conservative read-ahead path won't kick in here on this code. This is much trickier. */ +#define TINFL_HUFF_DECODE(state_index, sym, pHuff) \ + do \ + { \ + int temp; \ + mz_uint code_len, c; \ + if (num_bits < 15) \ + { \ + if ((pIn_buf_end - pIn_buf_cur) < 2) \ + { \ + TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ + } \ + else \ + { \ + bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); \ + pIn_buf_cur += 2; \ + num_bits += 16; \ + } \ + } \ + if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ + code_len = temp >> 9, temp &= 511; \ + else \ + { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do \ + { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while (temp < 0); \ + } \ + sym = temp; \ + bit_buf >>= code_len; \ + num_bits -= code_len; \ + } \ + MZ_MACRO_END + +tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags) +{ + static const int s_length_base[31] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 }; + static const int s_length_extra[31] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0 }; + static const int s_dist_base[32] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0 }; + static const int s_dist_extra[32] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 }; + static const mz_uint8 s_length_dezigzag[19] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + static const int s_min_table_sizes[3] = { 257, 1, 4 }; + + tinfl_status status = TINFL_STATUS_FAILED; + mz_uint32 num_bits, dist, counter, num_extra; + tinfl_bit_buf_t bit_buf; + const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; + mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; + size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; + + /* Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). */ + if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) + { + *pIn_buf_size = *pOut_buf_size = 0; + return TINFL_STATUS_BAD_PARAM; + } + + num_bits = r->m_num_bits; + bit_buf = r->m_bit_buf; + dist = r->m_dist; + counter = r->m_counter; + num_extra = r->m_num_extra; + dist_from_out_buf_start = r->m_dist_from_out_buf_start; + TINFL_CR_BEGIN + + bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; + r->m_z_adler32 = r->m_check_adler32 = 1; + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + TINFL_GET_BYTE(1, r->m_zhdr0); + TINFL_GET_BYTE(2, r->m_zhdr1); + counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); + if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); + if (counter) + { + TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); + } + } + + do + { + TINFL_GET_BITS(3, r->m_final, 3); + r->m_type = r->m_final >> 1; + if (r->m_type == 0) + { + TINFL_SKIP_BITS(5, num_bits & 7); + for (counter = 0; counter < 4; ++counter) + { + if (num_bits) + TINFL_GET_BITS(6, r->m_raw_header[counter], 8); + else + TINFL_GET_BYTE(7, r->m_raw_header[counter]); + } + if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) + { + TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); + } + while ((counter) && (num_bits)) + { + TINFL_GET_BITS(51, dist, 8); + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = (mz_uint8)dist; + counter--; + } + while (counter) + { + size_t n; + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); + } + while (pIn_buf_cur >= pIn_buf_end) + { + TINFL_CR_RETURN(38, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); + } + n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter); + TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); + pIn_buf_cur += n; + pOut_buf_cur += n; + counter -= (mz_uint)n; + } + } + else if (r->m_type == 3) + { + TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); + } + else + { + if (r->m_type == 1) + { + mz_uint8 *p = r->m_tables[0].m_code_size; + mz_uint i; + r->m_table_sizes[0] = 288; + r->m_table_sizes[1] = 32; + TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); + for (i = 0; i <= 143; ++i) + *p++ = 8; + for (; i <= 255; ++i) + *p++ = 9; + for (; i <= 279; ++i) + *p++ = 7; + for (; i <= 287; ++i) + *p++ = 8; + } + else + { + for (counter = 0; counter < 3; counter++) + { + TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); + r->m_table_sizes[counter] += s_min_table_sizes[counter]; + } + MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); + for (counter = 0; counter < r->m_table_sizes[2]; counter++) + { + mz_uint s; + TINFL_GET_BITS(14, s, 3); + r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; + } + r->m_table_sizes[2] = 19; + } + for (; (int)r->m_type >= 0; r->m_type--) + { + int tree_next, tree_cur; + tinfl_huff_table *pTable; + mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; + pTable = &r->m_tables[r->m_type]; + MZ_CLEAR_OBJ(total_syms); + MZ_CLEAR_OBJ(pTable->m_look_up); + MZ_CLEAR_OBJ(pTable->m_tree); + for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) + total_syms[pTable->m_code_size[i]]++; + used_syms = 0, total = 0; + next_code[0] = next_code[1] = 0; + for (i = 1; i <= 15; ++i) + { + used_syms += total_syms[i]; + next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); + } + if ((65536 != total) && (used_syms > 1)) + { + TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); + } + for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) + { + mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; + if (!code_size) + continue; + cur_code = next_code[code_size]++; + for (l = code_size; l > 0; l--, cur_code >>= 1) + rev_code = (rev_code << 1) | (cur_code & 1); + if (code_size <= TINFL_FAST_LOOKUP_BITS) + { + mz_int16 k = (mz_int16)((code_size << 9) | sym_index); + while (rev_code < TINFL_FAST_LOOKUP_SIZE) + { + pTable->m_look_up[rev_code] = k; + rev_code += (1 << code_size); + } + continue; + } + if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) + { + pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; + tree_cur = tree_next; + tree_next -= 2; + } + rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); + for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) + { + tree_cur -= ((rev_code >>= 1) & 1); + if (!pTable->m_tree[-tree_cur - 1]) + { + pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; + tree_cur = tree_next; + tree_next -= 2; + } + else + tree_cur = pTable->m_tree[-tree_cur - 1]; + } + tree_cur -= ((rev_code >>= 1) & 1); + pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; + } + if (r->m_type == 2) + { + for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]);) + { + mz_uint s; + TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); + if (dist < 16) + { + r->m_len_codes[counter++] = (mz_uint8)dist; + continue; + } + if ((dist == 16) && (!counter)) + { + TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); + } + num_extra = "\02\03\07"[dist - 16]; + TINFL_GET_BITS(18, s, num_extra); + s += "\03\03\013"[dist - 16]; + TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); + counter += s; + } + if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) + { + TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); + } + TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); + TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); + } + } + for (;;) + { + mz_uint8 *pSrc; + for (;;) + { + if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) + { + TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); + if (counter >= 256) + break; + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = (mz_uint8)counter; + } + else + { + int sym2; + mz_uint code_len; +#if TINFL_USE_64BIT_BITBUF + if (num_bits < 30) + { + bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 4; + num_bits += 32; + } +#else + if (num_bits < 15) + { + bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 2; + num_bits += 16; + } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; + do + { + sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; + } while (sym2 < 0); + } + counter = sym2; + bit_buf >>= code_len; + num_bits -= code_len; + if (counter & 256) + break; + +#if !TINFL_USE_64BIT_BITBUF + if (num_bits < 15) + { + bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 2; + num_bits += 16; + } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; + do + { + sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; + } while (sym2 < 0); + } + bit_buf >>= code_len; + num_bits -= code_len; + + pOut_buf_cur[0] = (mz_uint8)counter; + if (sym2 & 256) + { + pOut_buf_cur++; + counter = sym2; + break; + } + pOut_buf_cur[1] = (mz_uint8)sym2; + pOut_buf_cur += 2; + } + } + if ((counter &= 511) == 256) + break; + + num_extra = s_length_extra[counter - 257]; + counter = s_length_base[counter - 257]; + if (num_extra) + { + mz_uint extra_bits; + TINFL_GET_BITS(25, extra_bits, num_extra); + counter += extra_bits; + } + + TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); + num_extra = s_dist_extra[dist]; + dist = s_dist_base[dist]; + if (num_extra) + { + mz_uint extra_bits; + TINFL_GET_BITS(27, extra_bits, num_extra); + dist += extra_bits; + } + + dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; + if ((dist > dist_from_out_buf_start) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + { + TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); + } + + pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); + + if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) + { + while (counter--) + { + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; + } + continue; + } +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + else if ((counter >= 9) && (counter <= dist)) + { + const mz_uint8 *pSrc_end = pSrc + (counter & ~7); + do + { + ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; + ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; + pOut_buf_cur += 8; + } while ((pSrc += 8) < pSrc_end); + if ((counter &= 7) < 3) + { + if (counter) + { + pOut_buf_cur[0] = pSrc[0]; + if (counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + continue; + } + } +#endif + while(counter>2) + { + pOut_buf_cur[0] = pSrc[0]; + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur[2] = pSrc[2]; + pOut_buf_cur += 3; + pSrc += 3; + counter -= 3; + } + if (counter > 0) + { + pOut_buf_cur[0] = pSrc[0]; + if (counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + } + } + } while (!(r->m_final & 1)); + + /* Ensure byte alignment and put back any bytes from the bitbuf if we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */ + /* I'm being super conservative here. A number of simplifications can be made to the byte alignment part, and the Adler32 check shouldn't ever need to worry about reading from the bitbuf now. */ + TINFL_SKIP_BITS(32, num_bits & 7); + while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8)) + { + --pIn_buf_cur; + num_bits -= 8; + } + bit_buf &= (tinfl_bit_buf_t)((((mz_uint64)1) << num_bits) - (mz_uint64)1); + MZ_ASSERT(!num_bits); /* if this assert fires then we've read beyond the end of non-deflate/zlib streams with following data (such as gzip streams). */ + + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + for (counter = 0; counter < 4; ++counter) + { + mz_uint s; + if (num_bits) + TINFL_GET_BITS(41, s, 8); + else + TINFL_GET_BYTE(42, s); + r->m_z_adler32 = (r->m_z_adler32 << 8) | s; + } + } + TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); + + TINFL_CR_FINISH + +common_exit: + /* As long as we aren't telling the caller that we NEED more input to make forward progress: */ + /* Put back any bytes from the bitbuf in case we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */ + /* We need to be very careful here to NOT push back any bytes we definitely know we need to make forward progress, though, or we'll lock the caller up into an inf loop. */ + if ((status != TINFL_STATUS_NEEDS_MORE_INPUT) && (status != TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS)) + { + while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8)) + { + --pIn_buf_cur; + num_bits -= 8; + } + } + r->m_num_bits = num_bits; + r->m_bit_buf = bit_buf & (tinfl_bit_buf_t)((((mz_uint64)1) << num_bits) - (mz_uint64)1); + r->m_dist = dist; + r->m_counter = counter; + r->m_num_extra = num_extra; + r->m_dist_from_out_buf_start = dist_from_out_buf_start; + *pIn_buf_size = pIn_buf_cur - pIn_buf_next; + *pOut_buf_size = pOut_buf_cur - pOut_buf_next; + if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0)) + { + const mz_uint8 *ptr = pOut_buf_next; + size_t buf_len = *pOut_buf_size; + mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; + size_t block_len = buf_len % 5552; + while (buf_len) + { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) + { + s1 += ptr[0], s2 += s1; + s1 += ptr[1], s2 += s1; + s1 += ptr[2], s2 += s1; + s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; + s1 += ptr[5], s2 += s1; + s1 += ptr[6], s2 += s1; + s1 += ptr[7], s2 += s1; + } + for (; i < block_len; ++i) + s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; + buf_len -= block_len; + block_len = 5552; + } + r->m_check_adler32 = (s2 << 16) + s1; + if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) + status = TINFL_STATUS_ADLER32_MISMATCH; + } + return status; +} + +/* Higher level helper functions. */ +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + tinfl_decompressor decomp; + void *pBuf = NULL, *pNew_buf; + size_t src_buf_ofs = 0, out_buf_capacity = 0; + *pOut_len = 0; + tinfl_init(&decomp); + for (;;) + { + size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; + tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8 *)pBuf, pBuf ? (mz_uint8 *)pBuf + *pOut_len : NULL, &dst_buf_size, + (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) + { + MZ_FREE(pBuf); + *pOut_len = 0; + return NULL; + } + src_buf_ofs += src_buf_size; + *pOut_len += dst_buf_size; + if (status == TINFL_STATUS_DONE) + break; + new_out_buf_capacity = out_buf_capacity * 2; + if (new_out_buf_capacity < 128) + new_out_buf_capacity = 128; + pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); + if (!pNew_buf) + { + MZ_FREE(pBuf); + *pOut_len = 0; + return NULL; + } + pBuf = pNew_buf; + out_buf_capacity = new_out_buf_capacity; + } + return pBuf; +} + +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) +{ + tinfl_decompressor decomp; + tinfl_status status; + tinfl_init(&decomp); + status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf, &src_buf_len, (mz_uint8 *)pOut_buf, (mz_uint8 *)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len; +} + +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + int result = 0; + tinfl_decompressor decomp; + mz_uint8 *pDict = (mz_uint8 *)MZ_MALLOC(TINFL_LZ_DICT_SIZE); + size_t in_buf_ofs = 0, dict_ofs = 0; + if (!pDict) + return TINFL_STATUS_FAILED; + tinfl_init(&decomp); + for (;;) + { + size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; + tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, + (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); + in_buf_ofs += in_buf_size; + if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) + break; + if (status != TINFL_STATUS_HAS_MORE_OUTPUT) + { + result = (status == TINFL_STATUS_DONE); + break; + } + dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); + } + MZ_FREE(pDict); + *pIn_buf_size = in_buf_ofs; + return result; +} + +tinfl_decompressor *tinfl_decompressor_alloc() +{ + tinfl_decompressor *pDecomp = (tinfl_decompressor *)MZ_MALLOC(sizeof(tinfl_decompressor)); + if (pDecomp) + tinfl_init(pDecomp); + return pDecomp; +} + +void tinfl_decompressor_free(tinfl_decompressor *pDecomp) +{ + MZ_FREE(pDecomp); +} + +#ifdef __cplusplus +} +#endif +/************************************************************************** + * + * Copyright 2013-2014 RAD Game Tools and Valve Software + * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + * Copyright 2016 Martin Raiber + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + +#ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- .ZIP archive reading */ + +#ifdef MINIZ_NO_STDIO +#define MZ_FILE void * +#else +#include + +#if defined(_MSC_VER) || defined(__MINGW64__) +static FILE *mz_fopen(const char *pFilename, const char *pMode) +{ + FILE *pFile = NULL; + fopen_s(&pFile, pFilename, pMode); + return pFile; +} +static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) +{ + FILE *pFile = NULL; + if (freopen_s(&pFile, pPath, pMode, pStream)) + return NULL; + return pFile; +} +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN mz_fopen +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 _ftelli64 +#define MZ_FSEEK64 _fseeki64 +#define MZ_FILE_STAT_STRUCT _stat +#define MZ_FILE_STAT _stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN mz_freopen +#define MZ_DELETE_FILE remove +#elif defined(__MINGW32__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello64 +#define MZ_FSEEK64 fseeko64 +#define MZ_FILE_STAT_STRUCT _stat +#define MZ_FILE_STAT _stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#elif defined(__TINYC__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftell +#define MZ_FSEEK64 fseek +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#elif defined(__GNUC__) && _LARGEFILE64_SOURCE +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen64(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello64 +#define MZ_FSEEK64 fseeko64 +#define MZ_FILE_STAT_STRUCT stat64 +#define MZ_FILE_STAT stat64 +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(p, m, s) freopen64(p, m, s) +#define MZ_DELETE_FILE remove +#elif defined(__APPLE__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello +#define MZ_FSEEK64 fseeko +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(p, m, s) freopen(p, m, s) +#define MZ_DELETE_FILE remove + +#else +#pragma message("Using fopen, ftello, fseeko, stat() etc. path for file I/O - this path may not support large files.") +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#ifdef __STRICT_ANSI__ +#define MZ_FTELL64 ftell +#define MZ_FSEEK64 fseek +#else +#define MZ_FTELL64 ftello +#define MZ_FSEEK64 fseeko +#endif +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#endif /* #ifdef _MSC_VER */ +#endif /* #ifdef MINIZ_NO_STDIO */ + +#define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) + +/* Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff. */ +enum +{ + /* ZIP archive identifiers and record sizes */ + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, + MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, + + /* ZIP64 archive identifier and record sizes */ + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG = 0x07064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE = 56, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE = 20, + MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID = 0x0001, + MZ_ZIP_DATA_DESCRIPTOR_ID = 0x08074b50, + MZ_ZIP_DATA_DESCRIPTER_SIZE64 = 24, + MZ_ZIP_DATA_DESCRIPTER_SIZE32 = 16, + + /* Central directory header record offsets */ + MZ_ZIP_CDH_SIG_OFS = 0, + MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, + MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, + MZ_ZIP_CDH_BIT_FLAG_OFS = 8, + MZ_ZIP_CDH_METHOD_OFS = 10, + MZ_ZIP_CDH_FILE_TIME_OFS = 12, + MZ_ZIP_CDH_FILE_DATE_OFS = 14, + MZ_ZIP_CDH_CRC32_OFS = 16, + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, + MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, + MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, + MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, + MZ_ZIP_CDH_DISK_START_OFS = 34, + MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, + MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, + + /* Local directory header offsets */ + MZ_ZIP_LDH_SIG_OFS = 0, + MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, + MZ_ZIP_LDH_BIT_FLAG_OFS = 6, + MZ_ZIP_LDH_METHOD_OFS = 8, + MZ_ZIP_LDH_FILE_TIME_OFS = 10, + MZ_ZIP_LDH_FILE_DATE_OFS = 12, + MZ_ZIP_LDH_CRC32_OFS = 14, + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, + MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, + MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, + MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR = 1 << 3, + + /* End of central directory offsets */ + MZ_ZIP_ECDH_SIG_OFS = 0, + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, + MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, + MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, + MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, + + /* ZIP64 End of central directory locator offsets */ + MZ_ZIP64_ECDL_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDL_NUM_DISK_CDIR_OFS = 4, /* 4 bytes */ + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS = 8, /* 8 bytes */ + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS = 16, /* 4 bytes */ + + /* ZIP64 End of central directory header offsets */ + MZ_ZIP64_ECDH_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS = 4, /* 8 bytes */ + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS = 12, /* 2 bytes */ + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS = 14, /* 2 bytes */ + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS = 16, /* 4 bytes */ + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS = 20, /* 4 bytes */ + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 24, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS = 32, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_SIZE_OFS = 40, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_OFS_OFS = 48, /* 8 bytes */ + MZ_ZIP_VERSION_MADE_BY_DOS_FILESYSTEM_ID = 0, + MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG = 0x10, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED = 1, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG = 32, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION = 64, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED = 8192, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8 = 1 << 11 +}; + +typedef struct +{ + void *m_p; + size_t m_size, m_capacity; + mz_uint m_element_size; +} mz_zip_array; + +struct mz_zip_internal_state_tag +{ + mz_zip_array m_central_dir; + mz_zip_array m_central_dir_offsets; + mz_zip_array m_sorted_central_dir_offsets; + + /* The flags passed in when the archive is initially opened. */ + uint32_t m_init_flags; + + /* MZ_TRUE if the archive has a zip64 end of central directory headers, etc. */ + mz_bool m_zip64; + + /* MZ_TRUE if we found zip64 extended info in the central directory (m_zip64 will also be slammed to true too, even if we didn't find a zip64 end of central dir header, etc.) */ + mz_bool m_zip64_has_extended_info_fields; + + /* These fields are used by the file, FILE, memory, and memory/heap read/write helpers. */ + MZ_FILE *m_pFile; + mz_uint64 m_file_archive_start_ofs; + + void *m_pMem; + size_t m_mem_size; + size_t m_mem_capacity; +}; + +#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size + +#if defined(DEBUG) || defined(_DEBUG) || defined(NDEBUG) +static MZ_FORCEINLINE mz_uint mz_zip_array_range_check(const mz_zip_array *pArray, mz_uint index) +{ + MZ_ASSERT(index < pArray->m_size); + return index; +} +#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[mz_zip_array_range_check(array_ptr, index)] +#else +#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index] +#endif + +static MZ_FORCEINLINE void mz_zip_array_init(mz_zip_array *pArray, mz_uint32 element_size) +{ + memset(pArray, 0, sizeof(mz_zip_array)); + pArray->m_element_size = element_size; +} + +static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray) +{ + pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); + memset(pArray, 0, sizeof(mz_zip_array)); +} + +static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing) +{ + void *pNew_p; + size_t new_capacity = min_new_capacity; + MZ_ASSERT(pArray->m_element_size); + if (pArray->m_capacity >= min_new_capacity) + return MZ_TRUE; + if (growing) + { + new_capacity = MZ_MAX(1, pArray->m_capacity); + while (new_capacity < min_new_capacity) + new_capacity *= 2; + } + if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity))) + return MZ_FALSE; + pArray->m_p = pNew_p; + pArray->m_capacity = new_capacity; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing) +{ + if (new_capacity > pArray->m_capacity) + { + if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) + return MZ_FALSE; + } + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing) +{ + if (new_size > pArray->m_capacity) + { + if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) + return MZ_FALSE; + } + pArray->m_size = new_size; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n) +{ + return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE); +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n) +{ + size_t orig_size = pArray->m_size; + if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) + return MZ_FALSE; + memcpy((mz_uint8 *)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size); + return MZ_TRUE; +} + +#ifndef MINIZ_NO_TIME +static MZ_TIME_T mz_zip_dos_to_time_t(int dos_time, int dos_date) +{ + struct tm tm; + memset(&tm, 0, sizeof(tm)); + tm.tm_isdst = -1; + tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; + tm.tm_mon = ((dos_date >> 5) & 15) - 1; + tm.tm_mday = dos_date & 31; + tm.tm_hour = (dos_time >> 11) & 31; + tm.tm_min = (dos_time >> 5) & 63; + tm.tm_sec = (dos_time << 1) & 62; + return mktime(&tm); +} + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS +static void mz_zip_time_t_to_dos_time(MZ_TIME_T time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) +{ +#ifdef _MSC_VER + struct tm tm_struct; + struct tm *tm = &tm_struct; + errno_t err = localtime_s(tm, &time); + if (err) + { + *pDOS_date = 0; + *pDOS_time = 0; + return; + } +#else + struct tm *tm = localtime(&time); +#endif /* #ifdef _MSC_VER */ + + *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1)); + *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday); +} +#endif /* MINIZ_NO_ARCHIVE_WRITING_APIS */ + +#ifndef MINIZ_NO_STDIO +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS +static mz_bool mz_zip_get_file_modified_time(const char *pFilename, MZ_TIME_T *pTime) +{ + struct MZ_FILE_STAT_STRUCT file_stat; + + /* On Linux with x86 glibc, this call will fail on large files (I think >= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. */ + if (MZ_FILE_STAT(pFilename, &file_stat) != 0) + return MZ_FALSE; + + *pTime = file_stat.st_mtime; + + return MZ_TRUE; +} +#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS*/ + +static mz_bool mz_zip_set_file_times(const char *pFilename, MZ_TIME_T access_time, MZ_TIME_T modified_time) +{ + struct utimbuf t; + + memset(&t, 0, sizeof(t)); + t.actime = access_time; + t.modtime = modified_time; + + return !utime(pFilename, &t); +} +#endif /* #ifndef MINIZ_NO_STDIO */ +#endif /* #ifndef MINIZ_NO_TIME */ + +static MZ_FORCEINLINE mz_bool mz_zip_set_error(mz_zip_archive *pZip, mz_zip_error err_num) +{ + if (pZip) + pZip->m_last_error = err_num; + return MZ_FALSE; +} + +static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint flags) +{ + (void)flags; + if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!pZip->m_pAlloc) + pZip->m_pAlloc = miniz_def_alloc_func; + if (!pZip->m_pFree) + pZip->m_pFree = miniz_def_free_func; + if (!pZip->m_pRealloc) + pZip->m_pRealloc = miniz_def_realloc_func; + + pZip->m_archive_size = 0; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + pZip->m_last_error = MZ_ZIP_NO_ERROR; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + pZip->m_pState->m_init_flags = flags; + pZip->m_pState->m_zip64 = MZ_FALSE; + pZip->m_pState->m_zip64_has_extended_info_fields = MZ_FALSE; + + pZip->m_zip_mode = MZ_ZIP_MODE_READING; + + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index) +{ + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; + const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index)); + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; + pR++; + } + return (pL == pE) ? (l_len < r_len) : (l < r); +} + +#define MZ_SWAP_UINT32(a, b) \ + do \ + { \ + mz_uint32 t = a; \ + a = b; \ + b = t; \ + } \ + MZ_MACRO_END + +/* Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.) */ +static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices; + mz_uint32 start, end; + const mz_uint32 size = pZip->m_total_files; + + if (size <= 1U) + return; + + pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + + start = (size - 2U) >> 1U; + for (;;) + { + mz_uint64 child, root = start; + for (;;) + { + if ((child = (root << 1U) + 1U) >= size) + break; + child += (((child + 1U) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U]))); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); + root = child; + } + if (!start) + break; + start--; + } + + end = size - 1; + while (end > 0) + { + mz_uint64 child, root = 0; + MZ_SWAP_UINT32(pIndices[end], pIndices[0]); + for (;;) + { + if ((child = (root << 1U) + 1U) >= end) + break; + child += (((child + 1U) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U])); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); + root = child; + } + end--; + } +} + +static mz_bool mz_zip_reader_locate_header_sig(mz_zip_archive *pZip, mz_uint32 record_sig, mz_uint32 record_size, mz_int64 *pOfs) +{ + mz_int64 cur_file_ofs; + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; + mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + + /* Basic sanity checks - reject files which are too small */ + if (pZip->m_archive_size < record_size) + return MZ_FALSE; + + /* Find the record by scanning the file from the end towards the beginning. */ + cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); + for (;;) + { + int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); + + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) + return MZ_FALSE; + + for (i = n - 4; i >= 0; --i) + { + mz_uint s = MZ_READ_LE32(pBuf + i); + if (s == record_sig) + { + if ((pZip->m_archive_size - (cur_file_ofs + i)) >= record_size) + break; + } + } + + if (i >= 0) + { + cur_file_ofs += i; + break; + } + + /* Give up if we've searched the entire file, or we've gone back "too far" (~64kb) */ + if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (MZ_UINT16_MAX + record_size))) + return MZ_FALSE; + + cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); + } + + *pOfs = cur_file_ofs; + return MZ_TRUE; +} + +static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint flags) +{ + mz_uint cdir_size = 0, cdir_entries_on_this_disk = 0, num_this_disk = 0, cdir_disk_index = 0; + mz_uint64 cdir_ofs = 0; + mz_int64 cur_file_ofs = 0; + const mz_uint8 *p; + + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; + mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); + mz_uint32 zip64_end_of_central_dir_locator_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pZip64_locator = (mz_uint8 *)zip64_end_of_central_dir_locator_u32; + + mz_uint32 zip64_end_of_central_dir_header_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pZip64_end_of_central_dir = (mz_uint8 *)zip64_end_of_central_dir_header_u32; + + mz_uint64 zip64_end_of_central_dir_ofs = 0; + + /* Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. */ + if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (!mz_zip_reader_locate_header_sig(pZip, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE, &cur_file_ofs)) + return mz_zip_set_error(pZip, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR); + + /* Read and verify the end of central directory record. */ + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (cur_file_ofs >= (MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) + { + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs - MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE, pZip64_locator, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) + { + if (MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG) + { + zip64_end_of_central_dir_ofs = MZ_READ_LE64(pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS); + if (zip64_end_of_central_dir_ofs > (pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (pZip->m_pRead(pZip->m_pIO_opaque, zip64_end_of_central_dir_ofs, pZip64_end_of_central_dir, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) + { + if (MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG) + { + pZip->m_pState->m_zip64 = MZ_TRUE; + } + } + } + } + } + + pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS); + cdir_entries_on_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); + num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); + cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); + cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS); + cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); + + if (pZip->m_pState->m_zip64) + { + mz_uint32 zip64_total_num_of_disks = MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS); + mz_uint64 zip64_cdir_total_entries = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS); + mz_uint64 zip64_cdir_total_entries_on_this_disk = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); + mz_uint64 zip64_size_of_end_of_central_dir_record = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS); + mz_uint64 zip64_size_of_central_directory = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_SIZE_OFS); + + if (zip64_size_of_end_of_central_dir_record < (MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - 12)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (zip64_total_num_of_disks != 1U) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + /* Check for miniz's practical limits */ + if (zip64_cdir_total_entries > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + pZip->m_total_files = (mz_uint32)zip64_cdir_total_entries; + + if (zip64_cdir_total_entries_on_this_disk > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + cdir_entries_on_this_disk = (mz_uint32)zip64_cdir_total_entries_on_this_disk; + + /* Check for miniz's current practical limits (sorry, this should be enough for millions of files) */ + if (zip64_size_of_central_directory > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + cdir_size = (mz_uint32)zip64_size_of_central_directory; + + num_this_disk = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS); + + cdir_disk_index = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS); + + cdir_ofs = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_OFS_OFS); + } + + if (pZip->m_total_files != cdir_entries_on_this_disk) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1))) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (cdir_size < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pZip->m_central_directory_file_ofs = cdir_ofs; + + if (pZip->m_total_files) + { + mz_uint i, n; + /* Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and possibly another to hold the sorted indices. */ + if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) || + (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (sort_central_dir) + { + if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + /* Now create an index into the central directory file records, do some basic sanity checking on each record */ + p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; + for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) + { + mz_uint total_header_size, disk_index, bit_flags, filename_size, ext_data_size; + mz_uint64 comp_size, decomp_size, local_header_ofs; + + if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p); + + if (sort_central_dir) + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i; + + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + filename_size = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + ext_data_size = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); + + if ((!pZip->m_pState->m_zip64_has_extended_info_fields) && + (ext_data_size) && + (MZ_MAX(MZ_MAX(comp_size, decomp_size), local_header_ofs) == MZ_UINT32_MAX)) + { + /* Attempt to find zip64 extended information field in the entry's extra data */ + mz_uint32 extra_size_remaining = ext_data_size; + + if (extra_size_remaining) + { + const mz_uint8 *pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size; + + do + { + mz_uint32 field_id; + mz_uint32 field_data_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + + if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + /* Ok, the archive didn't have any zip64 headers but it uses a zip64 extended information field so mark it as zip64 anyway (this can occur with infozip's zip util when it reads compresses files from stdin). */ + pZip->m_pState->m_zip64 = MZ_TRUE; + pZip->m_pState->m_zip64_has_extended_info_fields = MZ_TRUE; + break; + } + + pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; + extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; + } while (extra_size_remaining); + } + } + + /* I've seen archives that aren't marked as zip64 that uses zip64 ext data, argh */ + if ((comp_size != MZ_UINT32_MAX) && (decomp_size != MZ_UINT32_MAX)) + { + if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); + if ((disk_index == MZ_UINT16_MAX) || ((disk_index != num_this_disk) && (disk_index != 1))) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (comp_size != MZ_UINT32_MAX) + { + if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + bit_flags = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + if (bit_flags & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + n -= total_header_size; + p += total_header_size; + } + } + + if (sort_central_dir) + mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); + + return MZ_TRUE; +} + +void mz_zip_zero_struct(mz_zip_archive *pZip) +{ + if (pZip) + MZ_CLEAR_OBJ(*pZip); +} + +static mz_bool mz_zip_reader_end_internal(mz_zip_archive *pZip, mz_bool set_last_error) +{ + mz_bool status = MZ_TRUE; + + if (!pZip) + return MZ_FALSE; + + if ((!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + { + if (set_last_error) + pZip->m_last_error = MZ_ZIP_INVALID_PARAMETER; + + return MZ_FALSE; + } + + if (pZip->m_pState) + { + mz_zip_internal_state *pState = pZip->m_pState; + pZip->m_pState = NULL; + + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) + { + if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) + { + if (MZ_FCLOSE(pState->m_pFile) == EOF) + { + if (set_last_error) + pZip->m_last_error = MZ_ZIP_FILE_CLOSE_FAILED; + status = MZ_FALSE; + } + } + pState->m_pFile = NULL; + } +#endif /* #ifndef MINIZ_NO_STDIO */ + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + } + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + + return status; +} + +mz_bool mz_zip_reader_end(mz_zip_archive *pZip) +{ + return mz_zip_reader_end_internal(pZip, MZ_TRUE); +} +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags) +{ + if ((!pZip) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_USER; + pZip->m_archive_size = size; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n); + memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s); + return s; +} + +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags) +{ + if (!pMem) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_MEMORY; + pZip->m_archive_size = size; + pZip->m_pRead = mz_zip_mem_read_func; + pZip->m_pIO_opaque = pZip; + pZip->m_pNeeds_keepalive = NULL; + +#ifdef __cplusplus + pZip->m_pState->m_pMem = const_cast(pMem); +#else + pZip->m_pState->m_pMem = (void *)pMem; +#endif + + pZip->m_pState->m_mem_size = size; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + + file_ofs += pZip->m_pState->m_file_archive_start_ofs; + + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + + return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags) +{ + return mz_zip_reader_init_file_v2(pZip, pFilename, flags, 0, 0); +} + +mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size) +{ + mz_uint64 file_size; + MZ_FILE *pFile; + + if ((!pZip) || (!pFilename) || ((archive_size) && (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE))) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pFile = MZ_FOPEN(pFilename, "rb"); + if (!pFile) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + + file_size = archive_size; + if (!file_size) + { + if (MZ_FSEEK64(pFile, 0, SEEK_END)) + { + MZ_FCLOSE(pFile); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); + } + + file_size = MZ_FTELL64(pFile); + } + + /* TODO: Better sanity check archive_size and the # of actual remaining bytes */ + + if (file_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + { + MZ_FCLOSE(pFile); + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + } + + if (!mz_zip_reader_init_internal(pZip, flags)) + { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + + pZip->m_zip_type = MZ_ZIP_TYPE_FILE; + pZip->m_pRead = mz_zip_file_read_func; + pZip->m_pIO_opaque = pZip; + pZip->m_pState->m_pFile = pFile; + pZip->m_archive_size = file_size; + pZip->m_pState->m_file_archive_start_ofs = file_start_ofs; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags) +{ + mz_uint64 cur_file_ofs; + + if ((!pZip) || (!pFile)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + + cur_file_ofs = MZ_FTELL64(pFile); + + if (!archive_size) + { + if (MZ_FSEEK64(pFile, 0, SEEK_END)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); + + archive_size = MZ_FTELL64(pFile) - cur_file_ofs; + + if (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + } + + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_CFILE; + pZip->m_pRead = mz_zip_file_read_func; + + pZip->m_pIO_opaque = pZip; + pZip->m_pState->m_pFile = pFile; + pZip->m_archive_size = archive_size; + pZip->m_pState->m_file_archive_start_ofs = cur_file_ofs; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +#endif /* #ifndef MINIZ_NO_STDIO */ + +static MZ_FORCEINLINE const mz_uint8 *mz_zip_get_cdh(mz_zip_archive *pZip, mz_uint file_index) +{ + if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files)) + return NULL; + return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); +} + +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint m_bit_flag; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + return (m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) != 0; +} + +mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint bit_flag; + mz_uint method; + + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); + bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + + if ((method != 0) && (method != MZ_DEFLATED)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + return MZ_FALSE; + } + + if (bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + return MZ_FALSE; + } + + if (bit_flag & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint filename_len, attribute_mapping_id, external_attr; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_len) + { + if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') + return MZ_TRUE; + } + + /* Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. */ + /* Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. */ + /* FIXME: Remove this check? Is it necessary - we already check the filename. */ + attribute_mapping_id = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS) >> 8; + (void)attribute_mapping_id; + + external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + if ((external_attr & MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG) != 0) + { + return MZ_TRUE; + } + + return MZ_FALSE; +} + +static mz_bool mz_zip_file_stat_internal(mz_zip_archive *pZip, mz_uint file_index, const mz_uint8 *pCentral_dir_header, mz_zip_archive_file_stat *pStat, mz_bool *pFound_zip64_extra_data) +{ + mz_uint n; + const mz_uint8 *p = pCentral_dir_header; + + if (pFound_zip64_extra_data) + *pFound_zip64_extra_data = MZ_FALSE; + + if ((!p) || (!pStat)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Extract fields from the central directory record. */ + pStat->m_file_index = file_index; + pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index); + pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS); + pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS); + pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); +#ifndef MINIZ_NO_TIME + pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS)); +#endif + pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS); + pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS); + pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + + /* Copy as much of the filename and comment as possible. */ + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); + memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pStat->m_filename[n] = '\0'; + + n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); + n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); + pStat->m_comment_size = n; + memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n); + pStat->m_comment[n] = '\0'; + + /* Set some flags for convienance */ + pStat->m_is_directory = mz_zip_reader_is_file_a_directory(pZip, file_index); + pStat->m_is_encrypted = mz_zip_reader_is_file_encrypted(pZip, file_index); + pStat->m_is_supported = mz_zip_reader_is_file_supported(pZip, file_index); + + /* See if we need to read any zip64 extended information fields. */ + /* Confusingly, these zip64 fields can be present even on non-zip64 archives (Debian zip on a huge files from stdin piped to stdout creates them). */ + if (MZ_MAX(MZ_MAX(pStat->m_comp_size, pStat->m_uncomp_size), pStat->m_local_header_ofs) == MZ_UINT32_MAX) + { + /* Attempt to find zip64 extended information field in the entry's extra data */ + mz_uint32 extra_size_remaining = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); + + if (extra_size_remaining) + { + const mz_uint8 *pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + + do + { + mz_uint32 field_id; + mz_uint32 field_data_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + + if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + const mz_uint8 *pField_data = pExtra_data + sizeof(mz_uint16) * 2; + mz_uint32 field_data_remaining = field_data_size; + + if (pFound_zip64_extra_data) + *pFound_zip64_extra_data = MZ_TRUE; + + if (pStat->m_uncomp_size == MZ_UINT32_MAX) + { + if (field_data_remaining < sizeof(mz_uint64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pStat->m_uncomp_size = MZ_READ_LE64(pField_data); + pField_data += sizeof(mz_uint64); + field_data_remaining -= sizeof(mz_uint64); + } + + if (pStat->m_comp_size == MZ_UINT32_MAX) + { + if (field_data_remaining < sizeof(mz_uint64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pStat->m_comp_size = MZ_READ_LE64(pField_data); + pField_data += sizeof(mz_uint64); + field_data_remaining -= sizeof(mz_uint64); + } + + if (pStat->m_local_header_ofs == MZ_UINT32_MAX) + { + if (field_data_remaining < sizeof(mz_uint64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pStat->m_local_header_ofs = MZ_READ_LE64(pField_data); + pField_data += sizeof(mz_uint64); + field_data_remaining -= sizeof(mz_uint64); + } + + break; + } + + pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; + extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; + } while (extra_size_remaining); + } + } + + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags) +{ + mz_uint i; + if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE) + return 0 == memcmp(pA, pB, len); + for (i = 0; i < len; ++i) + if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i])) + return MZ_FALSE; + return MZ_TRUE; +} + +static MZ_FORCEINLINE int mz_zip_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len) +{ + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; + pR++; + } + return (pL == pE) ? (int)(l_len - r_len) : (l - r); +} + +static mz_bool mz_zip_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename, mz_uint32 *pIndex) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const uint32_t size = pZip->m_total_files; + const mz_uint filename_len = (mz_uint)strlen(pFilename); + + if (pIndex) + *pIndex = 0; + + if (size) + { + /* yes I could use uint32_t's, but then we would have to add some special case checks in the loop, argh, and */ + /* honestly the major expense here on 32-bit CPU's will still be the filename compare */ + mz_int64 l = 0, h = (mz_int64)size - 1; + + while (l <= h) + { + mz_int64 m = l + ((h - l) >> 1); + uint32_t file_index = pIndices[(uint32_t)m]; + + int comp = mz_zip_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len); + if (!comp) + { + if (pIndex) + *pIndex = file_index; + return MZ_TRUE; + } + else if (comp < 0) + l = m + 1; + else + h = m - 1; + } + } + + return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND); +} + +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags) +{ + mz_uint32 index; + if (!mz_zip_reader_locate_file_v2(pZip, pName, pComment, flags, &index)) + return -1; + else + return (int)index; +} + +mz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *pIndex) +{ + mz_uint file_index; + size_t name_len, comment_len; + + if (pIndex) + *pIndex = 0; + + if ((!pZip) || (!pZip->m_pState) || (!pName)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* See if we can use a binary search */ + if (((pZip->m_pState->m_init_flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0) && + (pZip->m_zip_mode == MZ_ZIP_MODE_READING) && + ((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) + { + return mz_zip_locate_file_binary_search(pZip, pName, pIndex); + } + + /* Locate the entry by scanning the entire central directory */ + name_len = strlen(pName); + if (name_len > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + comment_len = pComment ? strlen(pComment) : 0; + if (comment_len > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + for (file_index = 0; file_index < pZip->m_total_files; file_index++) + { + const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); + mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); + const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + if (filename_len < name_len) + continue; + if (comment_len) + { + mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS); + const char *pFile_comment = pFilename + filename_len + file_extra_len; + if ((file_comment_len != comment_len) || (!mz_zip_string_equal(pComment, pFile_comment, file_comment_len, flags))) + continue; + } + if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) + { + int ofs = filename_len - 1; + do + { + if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':')) + break; + } while (--ofs >= 0); + ofs++; + pFilename += ofs; + filename_len -= ofs; + } + if ((filename_len == name_len) && (mz_zip_string_equal(pName, pFilename, filename_len, flags))) + { + if (pIndex) + *pIndex = file_index; + return MZ_TRUE; + } + } + + return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND); +} + +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + int status = TINFL_STATUS_DONE; + mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; + mz_zip_archive_file_stat file_stat; + void *pRead_buf; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + tinfl_decompressor inflator; + + if ((!pZip) || (!pZip->m_pState) || ((buf_size) && (!pBuf)) || ((user_read_buf_size) && (!pUser_read_buf)) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + /* A directory or zero length file */ + if ((file_stat.m_is_directory) || (!file_stat.m_comp_size)) + return MZ_TRUE; + + /* Encryption and patch files are not supported. */ + if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + /* This function only supports decompressing stored and deflate. */ + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + + /* Ensure supplied output buffer is large enough. */ + needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; + if (buf_size < needed_size) + return mz_zip_set_error(pZip, MZ_ZIP_BUF_TOO_SMALL); + + /* Read and parse the local directory entry. */ + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + /* The file is stored or the caller has requested the compressed data. */ + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) == 0) + { + if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32) + return mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED); + } +#endif + + return MZ_TRUE; + } + + /* Decompress the file either directly from memory or from a file input buffer. */ + tinfl_init(&inflator); + + if (pZip->m_pState->m_pMem) + { + /* Read directly from the archive in memory. */ + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else if (pUser_read_buf) + { + /* Use a user provided read buffer. */ + if (!user_read_buf_size) + return MZ_FALSE; + pRead_buf = (mz_uint8 *)pUser_read_buf; + read_buf_size = user_read_buf_size; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + else + { + /* Temporarily allocate a read buffer. */ + read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); + if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + do + { + /* The size_t cast here should be OK because we've verified that the output buffer is >= file_stat.m_uncomp_size above */ + size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0)); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + out_buf_ofs += out_buf_size; + } while (status == TINFL_STATUS_NEEDS_MORE_INPUT); + + if (status == TINFL_STATUS_DONE) + { + /* Make sure the entire file was decompressed, and check its CRC. */ + if (out_buf_ofs != file_stat.m_uncomp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); + status = TINFL_STATUS_FAILED; + } +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + else if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32) + { + mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED); + status = TINFL_STATUS_FAILED; + } +#endif + } + + if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + return MZ_FALSE; + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size); +} + +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags) +{ + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, NULL, 0); +} + +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags) +{ + return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0); +} + +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags) +{ + mz_uint64 comp_size, uncomp_size, alloc_size; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + void *pBuf; + + if (pSize) + *pSize = 0; + + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return NULL; + } + + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + + alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size; + if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) + { + mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + return NULL; + } + + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + return NULL; + } + + if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, flags)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return NULL; + } + + if (pSize) + *pSize = (size_t)alloc_size; + return pBuf; +} + +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + { + if (pSize) + *pSize = 0; + return MZ_FALSE; + } + return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags); +} + +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) +{ + int status = TINFL_STATUS_DONE; + mz_uint file_crc32 = MZ_CRC32_INIT; + mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs; + mz_zip_archive_file_stat file_stat; + void *pRead_buf = NULL; + void *pWrite_buf = NULL; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + + if ((!pZip) || (!pZip->m_pState) || (!pCallback) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + /* A directory or zero length file */ + if ((file_stat.m_is_directory) || (!file_stat.m_comp_size)) + return MZ_TRUE; + + /* Encryption and patch files are not supported. */ + if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + /* This function only supports decompressing stored and deflate. */ + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + + /* Read and do some minimal validation of the local directory entry (this doesn't crack the zip64 stuff, which we already have from the central dir) */ + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + /* Decompress the file either directly from memory or from a file input buffer. */ + if (pZip->m_pState->m_pMem) + { + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else + { + read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + /* The file is stored or the caller has requested the compressed data. */ + if (pZip->m_pState->m_pMem) + { + if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); + status = TINFL_STATUS_FAILED; + } + else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size); +#endif + } + + cur_file_ofs += file_stat.m_comp_size; + out_buf_ofs += file_stat.m_comp_size; + comp_remaining = 0; + } + else + { + while (comp_remaining) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail); + } +#endif + + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + + cur_file_ofs += read_buf_avail; + out_buf_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + } + } + } + else + { + tinfl_decompressor inflator; + tinfl_init(&inflator); + + if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + status = TINFL_STATUS_FAILED; + } + else + { + do + { + mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress(&inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + + if (out_buf_size) + { + if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size) + { + mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size); +#endif + if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + } + } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT)); + } + } + + if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) + { + /* Make sure the entire file was decompressed, and check its CRC. */ + if (out_buf_ofs != file_stat.m_uncomp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); + status = TINFL_STATUS_FAILED; + } +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + else if (file_crc32 != file_stat.m_crc32) + { + mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); + status = TINFL_STATUS_FAILED; + } +#endif + } + + if (!pZip->m_pState->m_pMem) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + + if (pWrite_buf) + pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + return MZ_FALSE; + + return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags); +} + +mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags) +{ + mz_zip_reader_extract_iter_state *pState; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + + /* Argument sanity check */ + if ((!pZip) || (!pZip->m_pState)) + return NULL; + + /* Allocate an iterator status structure */ + pState = (mz_zip_reader_extract_iter_state*)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_reader_extract_iter_state)); + if (!pState) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + return NULL; + } + + /* Fetch file details */ + if (!mz_zip_reader_file_stat(pZip, file_index, &pState->file_stat)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + /* Encryption and patch files are not supported. */ + if (pState->file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + /* This function only supports decompressing stored and deflate. */ + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (pState->file_stat.m_method != 0) && (pState->file_stat.m_method != MZ_DEFLATED)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + /* Init state - save args */ + pState->pZip = pZip; + pState->flags = flags; + + /* Init state - reset variables to defaults */ + pState->status = TINFL_STATUS_DONE; +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + pState->file_crc32 = MZ_CRC32_INIT; +#endif + pState->read_buf_ofs = 0; + pState->out_buf_ofs = 0; + pState->pRead_buf = NULL; + pState->pWrite_buf = NULL; + pState->out_blk_remain = 0; + + /* Read and parse the local directory entry. */ + pState->cur_file_ofs = pState->file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, pState->cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + pState->cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((pState->cur_file_ofs + pState->file_stat.m_comp_size) > pZip->m_archive_size) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + /* Decompress the file either directly from memory or from a file input buffer. */ + if (pZip->m_pState->m_pMem) + { + pState->pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + pState->cur_file_ofs; + pState->read_buf_size = pState->read_buf_avail = pState->file_stat.m_comp_size; + pState->comp_remaining = pState->file_stat.m_comp_size; + } + else + { + if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))) + { + /* Decompression required, therefore intermediate read buffer required */ + pState->read_buf_size = MZ_MIN(pState->file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); + if (NULL == (pState->pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)pState->read_buf_size))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + } + else + { + /* Decompression not required - we will be reading directly into user buffer, no temp buf required */ + pState->read_buf_size = 0; + } + pState->read_buf_avail = 0; + pState->comp_remaining = pState->file_stat.m_comp_size; + } + + if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))) + { + /* Decompression required, init decompressor */ + tinfl_init( &pState->inflator ); + + /* Allocate write buffer */ + if (NULL == (pState->pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + if (pState->pRead_buf) + pZip->m_pFree(pZip->m_pAlloc_opaque, pState->pRead_buf); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + } + + return pState; +} + +mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags) +{ + mz_uint32 file_index; + + /* Locate file index by name */ + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + return NULL; + + /* Construct iterator */ + return mz_zip_reader_extract_iter_new(pZip, file_index, flags); +} + +size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size) +{ + size_t copied_to_caller = 0; + + /* Argument sanity check */ + if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState) || (!pvBuf)) + return 0; + + if ((pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method)) + { + /* The file is stored or the caller has requested the compressed data, calc amount to return. */ + copied_to_caller = MZ_MIN( buf_size, pState->comp_remaining ); + + /* Zip is in memory....or requires reading from a file? */ + if (pState->pZip->m_pState->m_pMem) + { + /* Copy data to caller's buffer */ + memcpy( pvBuf, pState->pRead_buf, copied_to_caller ); + pState->pRead_buf = ((mz_uint8*)pState->pRead_buf) + copied_to_caller; + } + else + { + /* Read directly into caller's buffer */ + if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pvBuf, copied_to_caller) != copied_to_caller) + { + /* Failed to read all that was asked for, flag failure and alert user */ + mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED); + pState->status = TINFL_STATUS_FAILED; + copied_to_caller = 0; + } + } + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + /* Compute CRC if not returning compressed data only */ + if (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, (const mz_uint8 *)pvBuf, copied_to_caller); +#endif + + /* Advance offsets, dec counters */ + pState->cur_file_ofs += copied_to_caller; + pState->out_buf_ofs += copied_to_caller; + pState->comp_remaining -= copied_to_caller; + } + else + { + do + { + /* Calc ptr to write buffer - given current output pos and block size */ + mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pState->pWrite_buf + (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + + /* Calc max output size - given current output pos and block size */ + size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + + if (!pState->out_blk_remain) + { + /* Read more data from file if none available (and reading from file) */ + if ((!pState->read_buf_avail) && (!pState->pZip->m_pState->m_pMem)) + { + /* Calc read size */ + pState->read_buf_avail = MZ_MIN(pState->read_buf_size, pState->comp_remaining); + if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pState->pRead_buf, (size_t)pState->read_buf_avail) != pState->read_buf_avail) + { + mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED); + pState->status = TINFL_STATUS_FAILED; + break; + } + + /* Advance offsets, dec counters */ + pState->cur_file_ofs += pState->read_buf_avail; + pState->comp_remaining -= pState->read_buf_avail; + pState->read_buf_ofs = 0; + } + + /* Perform decompression */ + in_buf_size = (size_t)pState->read_buf_avail; + pState->status = tinfl_decompress(&pState->inflator, (const mz_uint8 *)pState->pRead_buf + pState->read_buf_ofs, &in_buf_size, (mz_uint8 *)pState->pWrite_buf, pWrite_buf_cur, &out_buf_size, pState->comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); + pState->read_buf_avail -= in_buf_size; + pState->read_buf_ofs += in_buf_size; + + /* Update current output block size remaining */ + pState->out_blk_remain = out_buf_size; + } + + if (pState->out_blk_remain) + { + /* Calc amount to return. */ + size_t to_copy = MZ_MIN( (buf_size - copied_to_caller), pState->out_blk_remain ); + + /* Copy data to caller's buffer */ + memcpy( (uint8_t*)pvBuf + copied_to_caller, pWrite_buf_cur, to_copy ); + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + /* Perform CRC */ + pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, pWrite_buf_cur, to_copy); +#endif + + /* Decrement data consumed from block */ + pState->out_blk_remain -= to_copy; + + /* Inc output offset, while performing sanity check */ + if ((pState->out_buf_ofs += to_copy) > pState->file_stat.m_uncomp_size) + { + mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED); + pState->status = TINFL_STATUS_FAILED; + break; + } + + /* Increment counter of data copied to caller */ + copied_to_caller += to_copy; + } + } while ( (copied_to_caller < buf_size) && ((pState->status == TINFL_STATUS_NEEDS_MORE_INPUT) || (pState->status == TINFL_STATUS_HAS_MORE_OUTPUT)) ); + } + + /* Return how many bytes were copied into user buffer */ + return copied_to_caller; +} + +mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState) +{ + int status; + + /* Argument sanity check */ + if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState)) + return MZ_FALSE; + + /* Was decompression completed and requested? */ + if ((pState->status == TINFL_STATUS_DONE) && (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) + { + /* Make sure the entire file was decompressed, and check its CRC. */ + if (pState->out_buf_ofs != pState->file_stat.m_uncomp_size) + { + mz_zip_set_error(pState->pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); + pState->status = TINFL_STATUS_FAILED; + } +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + else if (pState->file_crc32 != pState->file_stat.m_crc32) + { + mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED); + pState->status = TINFL_STATUS_FAILED; + } +#endif + } + + /* Free buffers */ + if (!pState->pZip->m_pState->m_pMem) + pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pRead_buf); + if (pState->pWrite_buf) + pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pWrite_buf); + + /* Save status */ + status = pState->status; + + /* Free context */ + pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState); + + return status == TINFL_STATUS_DONE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n) +{ + (void)ofs; + + return MZ_FWRITE(pBuf, 1, n, (MZ_FILE *)pOpaque); +} + +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags) +{ + mz_bool status; + mz_zip_archive_file_stat file_stat; + MZ_FILE *pFile; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + if ((file_stat.m_is_directory) || (!file_stat.m_is_supported)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + + pFile = MZ_FOPEN(pDst_filename, "wb"); + if (!pFile) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + + status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); + + if (MZ_FCLOSE(pFile) == EOF) + { + if (status) + mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); + + status = MZ_FALSE; + } + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO) + if (status) + mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time); +#endif + + return status; +} + +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index)) + return MZ_FALSE; + + return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags); +} + +mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *pFile, mz_uint flags) +{ + mz_zip_archive_file_stat file_stat; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + if ((file_stat.m_is_directory) || (!file_stat.m_is_supported)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + + return mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); +} + +mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index)) + return MZ_FALSE; + + return mz_zip_reader_extract_to_cfile(pZip, file_index, pFile, flags); +} +#endif /* #ifndef MINIZ_NO_STDIO */ + +static size_t mz_zip_compute_crc32_callback(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_uint32 *p = (mz_uint32 *)pOpaque; + (void)file_ofs; + *p = (mz_uint32)mz_crc32(*p, (const mz_uint8 *)pBuf, n); + return n; +} + +mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags) +{ + mz_zip_archive_file_stat file_stat; + mz_zip_internal_state *pState; + const mz_uint8 *pCentral_dir_header; + mz_bool found_zip64_ext_data_in_cdir = MZ_FALSE; + mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + mz_uint64 local_header_ofs = 0; + mz_uint32 local_header_filename_len, local_header_extra_len, local_header_crc32; + mz_uint64 local_header_comp_size, local_header_uncomp_size; + mz_uint32 uncomp_crc32 = MZ_CRC32_INIT; + mz_bool has_data_descriptor; + mz_uint32 local_header_bit_flags; + + mz_zip_array file_data_array; + mz_zip_array_init(&file_data_array, 1); + + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (file_index > pZip->m_total_files) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + pCentral_dir_header = mz_zip_get_cdh(pZip, file_index); + + if (!mz_zip_file_stat_internal(pZip, file_index, pCentral_dir_header, &file_stat, &found_zip64_ext_data_in_cdir)) + return MZ_FALSE; + + /* A directory or zero length file */ + if ((file_stat.m_is_directory) || (!file_stat.m_uncomp_size)) + return MZ_TRUE; + + /* Encryption and patch files are not supported. */ + if (file_stat.m_is_encrypted) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + /* This function only supports stored and deflate. */ + if ((file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + + if (!file_stat.m_is_supported) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + + /* Read and parse the local directory entry. */ + local_header_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + local_header_filename_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS); + local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS); + local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS); + local_header_crc32 = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_CRC32_OFS); + local_header_bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); + has_data_descriptor = (local_header_bit_flags & 8) != 0; + + if (local_header_filename_len != strlen(file_stat.m_filename)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if ((local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (!mz_zip_array_resize(pZip, &file_data_array, MZ_MAX(local_header_filename_len, local_header_extra_len), MZ_FALSE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (local_header_filename_len) + { + if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE, file_data_array.m_p, local_header_filename_len) != local_header_filename_len) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + goto handle_failure; + } + + /* I've seen 1 archive that had the same pathname, but used backslashes in the local dir and forward slashes in the central dir. Do we care about this? For now, this case will fail validation. */ + if (memcmp(file_stat.m_filename, file_data_array.m_p, local_header_filename_len) != 0) + { + mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + goto handle_failure; + } + } + + if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX))) + { + mz_uint32 extra_size_remaining = local_header_extra_len; + const mz_uint8 *pExtra_data = (const mz_uint8 *)file_data_array.m_p; + + if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len, file_data_array.m_p, local_header_extra_len) != local_header_extra_len) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + goto handle_failure; + } + + do + { + mz_uint32 field_id, field_data_size, field_total_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + field_total_size = field_data_size + sizeof(mz_uint16) * 2; + + if (field_total_size > extra_size_remaining) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32); + + if (field_data_size < sizeof(mz_uint64) * 2) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + goto handle_failure; + } + + local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data); + local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64)); + + found_zip64_ext_data_in_ldir = MZ_TRUE; + break; + } + + pExtra_data += field_total_size; + extra_size_remaining -= field_total_size; + } while (extra_size_remaining); + } + + /* TODO: parse local header extra data when local_header_comp_size is 0xFFFFFFFF! (big_descriptor.zip) */ + /* I've seen zips in the wild with the data descriptor bit set, but proper local header values and bogus data descriptors */ + if ((has_data_descriptor) && (!local_header_comp_size) && (!local_header_crc32)) + { + mz_uint8 descriptor_buf[32]; + mz_bool has_id; + const mz_uint8 *pSrc; + mz_uint32 file_crc32; + mz_uint64 comp_size = 0, uncomp_size = 0; + + mz_uint32 num_descriptor_uint32s = ((pState->m_zip64) || (found_zip64_ext_data_in_ldir)) ? 6 : 4; + + if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size, descriptor_buf, sizeof(mz_uint32) * num_descriptor_uint32s) != (sizeof(mz_uint32) * num_descriptor_uint32s)) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + goto handle_failure; + } + + has_id = (MZ_READ_LE32(descriptor_buf) == MZ_ZIP_DATA_DESCRIPTOR_ID); + pSrc = has_id ? (descriptor_buf + sizeof(mz_uint32)) : descriptor_buf; + + file_crc32 = MZ_READ_LE32(pSrc); + + if ((pState->m_zip64) || (found_zip64_ext_data_in_ldir)) + { + comp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32)); + uncomp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32) + sizeof(mz_uint64)); + } + else + { + comp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32)); + uncomp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32) + sizeof(mz_uint32)); + } + + if ((file_crc32 != file_stat.m_crc32) || (comp_size != file_stat.m_comp_size) || (uncomp_size != file_stat.m_uncomp_size)) + { + mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + goto handle_failure; + } + } + else + { + if ((local_header_crc32 != file_stat.m_crc32) || (local_header_comp_size != file_stat.m_comp_size) || (local_header_uncomp_size != file_stat.m_uncomp_size)) + { + mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + goto handle_failure; + } + } + + mz_zip_array_clear(pZip, &file_data_array); + + if ((flags & MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY) == 0) + { + if (!mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_compute_crc32_callback, &uncomp_crc32, 0)) + return MZ_FALSE; + + /* 1 more check to be sure, although the extract checks too. */ + if (uncomp_crc32 != file_stat.m_crc32) + { + mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + return MZ_FALSE; + } + } + + return MZ_TRUE; + +handle_failure: + mz_zip_array_clear(pZip, &file_data_array); + return MZ_FALSE; +} + +mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags) +{ + mz_zip_internal_state *pState; + uint32_t i; + + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + /* Basic sanity checks */ + if (!pState->m_zip64) + { + if (pZip->m_total_files > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + if (pZip->m_archive_size > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + } + else + { + if (pZip->m_total_files >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + if (pState->m_central_dir.m_size >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + } + + for (i = 0; i < pZip->m_total_files; i++) + { + if (MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG & flags) + { + mz_uint32 found_index; + mz_zip_archive_file_stat stat; + + if (!mz_zip_reader_file_stat(pZip, i, &stat)) + return MZ_FALSE; + + if (!mz_zip_reader_locate_file_v2(pZip, stat.m_filename, NULL, 0, &found_index)) + return MZ_FALSE; + + /* This check can fail if there are duplicate filenames in the archive (which we don't check for when writing - that's up to the user) */ + if (found_index != i) + return mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + } + + if (!mz_zip_validate_file(pZip, i, flags)) + return MZ_FALSE; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr) +{ + mz_bool success = MZ_TRUE; + mz_zip_archive zip; + mz_zip_error actual_err = MZ_ZIP_NO_ERROR; + + if ((!pMem) || (!size)) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_PARAMETER; + return MZ_FALSE; + } + + mz_zip_zero_struct(&zip); + + if (!mz_zip_reader_init_mem(&zip, pMem, size, flags)) + { + if (pErr) + *pErr = zip.m_last_error; + return MZ_FALSE; + } + + if (!mz_zip_validate_archive(&zip, flags)) + { + actual_err = zip.m_last_error; + success = MZ_FALSE; + } + + if (!mz_zip_reader_end_internal(&zip, success)) + { + if (!actual_err) + actual_err = zip.m_last_error; + success = MZ_FALSE; + } + + if (pErr) + *pErr = actual_err; + + return success; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr) +{ + mz_bool success = MZ_TRUE; + mz_zip_archive zip; + mz_zip_error actual_err = MZ_ZIP_NO_ERROR; + + if (!pFilename) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_PARAMETER; + return MZ_FALSE; + } + + mz_zip_zero_struct(&zip); + + if (!mz_zip_reader_init_file_v2(&zip, pFilename, flags, 0, 0)) + { + if (pErr) + *pErr = zip.m_last_error; + return MZ_FALSE; + } + + if (!mz_zip_validate_archive(&zip, flags)) + { + actual_err = zip.m_last_error; + success = MZ_FALSE; + } + + if (!mz_zip_reader_end_internal(&zip, success)) + { + if (!actual_err) + actual_err = zip.m_last_error; + success = MZ_FALSE; + } + + if (pErr) + *pErr = actual_err; + + return success; +} +#endif /* #ifndef MINIZ_NO_STDIO */ + +/* ------------------- .ZIP archive writing */ + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +static MZ_FORCEINLINE void mz_write_le16(mz_uint8 *p, mz_uint16 v) +{ + p[0] = (mz_uint8)v; + p[1] = (mz_uint8)(v >> 8); +} +static MZ_FORCEINLINE void mz_write_le32(mz_uint8 *p, mz_uint32 v) +{ + p[0] = (mz_uint8)v; + p[1] = (mz_uint8)(v >> 8); + p[2] = (mz_uint8)(v >> 16); + p[3] = (mz_uint8)(v >> 24); +} +static MZ_FORCEINLINE void mz_write_le64(mz_uint8 *p, mz_uint64 v) +{ + mz_write_le32(p, (mz_uint32)v); + mz_write_le32(p + sizeof(mz_uint32), (mz_uint32)(v >> 32)); +} + +#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v)) +#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v)) +#define MZ_WRITE_LE64(p, v) mz_write_le64((mz_uint8 *)(p), (mz_uint64)(v)) + +static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size); + + if (!n) + return 0; + + /* An allocation this big is likely to just fail on 32-bit systems, so don't even go there. */ + if ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF)) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); + return 0; + } + + if (new_size > pState->m_mem_capacity) + { + void *pNew_block; + size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); + + while (new_capacity < new_size) + new_capacity *= 2; + + if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + return 0; + } + + pState->m_pMem = pNew_block; + pState->m_mem_capacity = new_capacity; + } + memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n); + pState->m_mem_size = (size_t)new_size; + return n; +} + +static mz_bool mz_zip_writer_end_internal(mz_zip_archive *pZip, mz_bool set_last_error) +{ + mz_zip_internal_state *pState; + mz_bool status = MZ_TRUE; + + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))) + { + if (set_last_error) + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + pState = pZip->m_pState; + pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) + { + if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) + { + if (MZ_FCLOSE(pState->m_pFile) == EOF) + { + if (set_last_error) + mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); + status = MZ_FALSE; + } + } + + pState->m_pFile = NULL; + } +#endif /* #ifndef MINIZ_NO_STDIO */ + + if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem); + pState->m_pMem = NULL; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + return status; +} + +mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags) +{ + mz_bool zip64 = (flags & MZ_ZIP_FLAG_WRITE_ZIP64) != 0; + + if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) + { + if (!pZip->m_pRead) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + } + + if (pZip->m_file_offset_alignment) + { + /* Ensure user specified file offset alignment is a power of 2. */ + if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + } + + if (!pZip->m_pAlloc) + pZip->m_pAlloc = miniz_def_alloc_func; + if (!pZip->m_pFree) + pZip->m_pFree = miniz_def_free_func; + if (!pZip->m_pRealloc) + pZip->m_pRealloc = miniz_def_realloc_func; + + pZip->m_archive_size = existing_size; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + + pZip->m_pState->m_zip64 = zip64; + pZip->m_pState->m_zip64_has_extended_info_fields = zip64; + + pZip->m_zip_type = MZ_ZIP_TYPE_USER; + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size) +{ + return mz_zip_writer_init_v2(pZip, existing_size, 0); +} + +mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags) +{ + pZip->m_pWrite = mz_zip_heap_write_func; + pZip->m_pNeeds_keepalive = NULL; + + if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) + pZip->m_pRead = mz_zip_mem_read_func; + + pZip->m_pIO_opaque = pZip; + + if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_HEAP; + + if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning))) + { + if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size))) + { + mz_zip_writer_end_internal(pZip, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + pZip->m_pState->m_mem_capacity = initial_allocation_size; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size) +{ + return mz_zip_writer_init_heap_v2(pZip, size_to_reserve_at_beginning, initial_allocation_size, 0); +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + + file_ofs += pZip->m_pState->m_file_archive_start_ofs; + + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); + return 0; + } + + return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning) +{ + return mz_zip_writer_init_file_v2(pZip, pFilename, size_to_reserve_at_beginning, 0); +} + +mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags) +{ + MZ_FILE *pFile; + + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pNeeds_keepalive = NULL; + + if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) + pZip->m_pRead = mz_zip_file_read_func; + + pZip->m_pIO_opaque = pZip; + + if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags)) + return MZ_FALSE; + + if (NULL == (pFile = MZ_FOPEN(pFilename, (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) ? "w+b" : "wb"))) + { + mz_zip_writer_end(pZip); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + } + + pZip->m_pState->m_pFile = pFile; + pZip->m_zip_type = MZ_ZIP_TYPE_FILE; + + if (size_to_reserve_at_beginning) + { + mz_uint64 cur_ofs = 0; + char buf[4096]; + + MZ_CLEAR_OBJ(buf); + + do + { + size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n) + { + mz_zip_writer_end(pZip); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + cur_ofs += n; + size_to_reserve_at_beginning -= n; + } while (size_to_reserve_at_beginning); + } + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags) +{ + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pNeeds_keepalive = NULL; + + if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) + pZip->m_pRead = mz_zip_file_read_func; + + pZip->m_pIO_opaque = pZip; + + if (!mz_zip_writer_init_v2(pZip, 0, flags)) + return MZ_FALSE; + + pZip->m_pState->m_pFile = pFile; + pZip->m_pState->m_file_archive_start_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + pZip->m_zip_type = MZ_ZIP_TYPE_CFILE; + + return MZ_TRUE; +} +#endif /* #ifndef MINIZ_NO_STDIO */ + +mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags) +{ + mz_zip_internal_state *pState; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (flags & MZ_ZIP_FLAG_WRITE_ZIP64) + { + /* We don't support converting a non-zip64 file to zip64 - this seems like more trouble than it's worth. (What about the existing 32-bit data descriptors that could follow the compressed data?) */ + if (!pZip->m_pState->m_zip64) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + } + + /* No sense in trying to write to an archive that's already at the support max size */ + if (pZip->m_pState->m_zip64) + { + if (pZip->m_total_files == MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + if (pZip->m_total_files == MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + if ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); + } + + pState = pZip->m_pState; + + if (pState->m_pFile) + { +#ifdef MINIZ_NO_STDIO + (void)pFilename; + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); +#else + if (pZip->m_pIO_opaque != pZip) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) + { + if (!pFilename) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Archive is being read from stdio and was originally opened only for reading. Try to reopen as writable. */ + if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile))) + { + /* The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it. */ + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + } + } + + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pNeeds_keepalive = NULL; +#endif /* #ifdef MINIZ_NO_STDIO */ + } + else if (pState->m_pMem) + { + /* Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback. */ + if (pZip->m_pIO_opaque != pZip) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState->m_mem_capacity = pState->m_mem_size; + pZip->m_pWrite = mz_zip_heap_write_func; + pZip->m_pNeeds_keepalive = NULL; + } + /* Archive is being read via a user provided read function - make sure the user has specified a write function too. */ + else if (!pZip->m_pWrite) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Start writing new files at the archive's current central directory location. */ + /* TODO: We could add a flag that lets the user start writing immediately AFTER the existing central dir - this would be safer. */ + pZip->m_archive_size = pZip->m_central_directory_file_ofs; + pZip->m_central_directory_file_ofs = 0; + + /* Clear the sorted central dir offsets, they aren't useful or maintained now. */ + /* Even though we're now in write mode, files can still be extracted and verified, but file locates will be slow. */ + /* TODO: We could easily maintain the sorted central directory offsets. */ + mz_zip_array_clear(pZip, &pZip->m_pState->m_sorted_central_dir_offsets); + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename) +{ + return mz_zip_writer_init_from_reader_v2(pZip, pFilename, 0); +} + +/* TODO: pArchive_name is a terrible name here! */ +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags) +{ + return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0); +} + +typedef struct +{ + mz_zip_archive *m_pZip; + mz_uint64 m_cur_archive_file_ofs; + mz_uint64 m_comp_size; +} mz_zip_writer_add_state; + +static mz_bool mz_zip_writer_add_put_buf_callback(const void *pBuf, int len, void *pUser) +{ + mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser; + if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len) + return MZ_FALSE; + + pState->m_cur_archive_file_ofs += len; + pState->m_comp_size += len; + return MZ_TRUE; +} + +#define MZ_ZIP64_MAX_LOCAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 2) +#define MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 3) +static mz_uint32 mz_zip_writer_create_zip64_extra_data(mz_uint8 *pBuf, mz_uint64 *pUncomp_size, mz_uint64 *pComp_size, mz_uint64 *pLocal_header_ofs) +{ + mz_uint8 *pDst = pBuf; + mz_uint32 field_size = 0; + + MZ_WRITE_LE16(pDst + 0, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID); + MZ_WRITE_LE16(pDst + 2, 0); + pDst += sizeof(mz_uint16) * 2; + + if (pUncomp_size) + { + MZ_WRITE_LE64(pDst, *pUncomp_size); + pDst += sizeof(mz_uint64); + field_size += sizeof(mz_uint64); + } + + if (pComp_size) + { + MZ_WRITE_LE64(pDst, *pComp_size); + pDst += sizeof(mz_uint64); + field_size += sizeof(mz_uint64); + } + + if (pLocal_header_ofs) + { + MZ_WRITE_LE64(pDst, *pLocal_header_ofs); + pDst += sizeof(mz_uint64); + field_size += sizeof(mz_uint64); + } + + MZ_WRITE_LE16(pBuf + 2, field_size); + + return (mz_uint32)(pDst - pBuf); +} + +static mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date) +{ + (void)pZip; + memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX)); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX)); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, + mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_size, + mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, + mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, + mz_uint64 local_header_ofs, mz_uint32 ext_attributes) +{ + (void)pZip; + memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX)); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX)); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_MIN(local_header_ofs, MZ_UINT32_MAX)); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size, + const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_size, + mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, + mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, + mz_uint64 local_header_ofs, mz_uint32 ext_attributes, + const char *user_extra_data, mz_uint user_extra_data_len) +{ + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size; + size_t orig_central_dir_size = pState->m_central_dir.m_size; + mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + + if (!pZip->m_pState->m_zip64) + { + if (local_header_ofs > 0xFFFFFFFF) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); + } + + /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ + if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + user_extra_data_len + comment_size) >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, extra_size + user_extra_data_len, comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_header_ofs, ext_attributes)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, user_extra_data, user_extra_data_len)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, ¢ral_dir_ofs, 1))) + { + /* Try to resize the central directory array back into its original state. */ + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name) +{ + /* Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes. */ + if (*pArchive_name == '/') + return MZ_FALSE; + + while (*pArchive_name) + { + if ((*pArchive_name == '\\') || (*pArchive_name == ':')) + return MZ_FALSE; + + pArchive_name++; + } + + return MZ_TRUE; +} + +static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip) +{ + mz_uint32 n; + if (!pZip->m_file_offset_alignment) + return 0; + n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1)); + return (mz_uint)((pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1)); +} + +static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n) +{ + char buf[4096]; + memset(buf, 0, MZ_MIN(sizeof(buf), n)); + while (n) + { + mz_uint32 s = MZ_MIN(sizeof(buf), n); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_file_ofs += s; + n -= s; + } + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + mz_uint64 uncomp_size, mz_uint32 uncomp_crc32) +{ + return mz_zip_writer_add_mem_ex_v2(pZip, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, uncomp_size, uncomp_crc32, NULL, NULL, 0, NULL, 0); +} + +mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, + mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, + const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) +{ + mz_uint16 method = 0, dos_time = 0, dos_date = 0; + mz_uint level, ext_attributes = 0, num_alignment_padding_bytes; + mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + tdefl_compressor *pComp = NULL; + mz_bool store_data_uncompressed; + mz_zip_internal_state *pState; + mz_uint8 *pExtra_data = NULL; + mz_uint32 extra_size = 0; + mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE]; + mz_uint16 bit_flags = 0; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + + if (uncomp_size || (buf_size && !(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) + bit_flags |= MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR; + + if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME)) + bit_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8; + + level = level_and_flags & 0xF; + store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)); + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + if (pState->m_zip64) + { + if (pZip->m_total_files == MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + if (pZip->m_total_files == MZ_UINT16_MAX) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */ + } + if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ + } + } + + if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); + +#ifndef MINIZ_NO_TIME + if (last_modified != NULL) + { + mz_zip_time_t_to_dos_time(*last_modified, &dos_time, &dos_date); + } + else + { + MZ_TIME_T cur_time; + time(&cur_time); + mz_zip_time_t_to_dos_time(cur_time, &dos_time, &dos_date); + } +#endif /* #ifndef MINIZ_NO_TIME */ + + if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { + uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, buf_size); + uncomp_size = buf_size; + if (uncomp_size <= 3) + { + level = 0; + store_data_uncompressed = MZ_TRUE; + } + } + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ + if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + if (!pState->m_zip64) + { + /* Bail early if the archive would obviously become too large */ + if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size + + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + user_extra_data_len + + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + user_extra_data_central_len + + MZ_ZIP_DATA_DESCRIPTER_SIZE32) > 0xFFFFFFFF) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ + } + } + + if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/')) + { + /* Set DOS Subdirectory attribute bit. */ + ext_attributes |= MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG; + + /* Subdirectories cannot contain data. */ + if ((buf_size) || (uncomp_size)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + } + + /* Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.) */ + if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + (pState->m_zip64 ? MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE : 0))) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if ((!store_data_uncompressed) && (buf_size)) + { + if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) + { + MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); + } + cur_archive_file_ofs += num_alignment_padding_bytes; + + MZ_CLEAR_OBJ(local_dir_header); + + if (!store_data_uncompressed || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { + method = MZ_DEFLATED; + } + + if (pState->m_zip64) + { + if (uncomp_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX) + { + pExtra_data = extra_data; + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, extra_size + user_extra_data_len, 0, 0, 0, method, bit_flags, dos_time, dos_date)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + cur_archive_file_ofs += archive_name_size; + + if (pExtra_data != NULL) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += extra_size; + } + } + else + { + if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, user_extra_data_len, 0, 0, 0, method, bit_flags, dos_time, dos_date)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + cur_archive_file_ofs += archive_name_size; + } + + if (user_extra_data_len > 0) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += user_extra_data_len; + } + + if (store_data_uncompressed) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_file_ofs += buf_size; + comp_size = buf_size; + } + else if (buf_size) + { + mz_zip_writer_add_state state; + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) || + (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED); + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pComp = NULL; + + if (uncomp_size) + { + mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64]; + mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32; + + MZ_ASSERT(bit_flags & MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR); + + MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID); + MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32); + if (pExtra_data == NULL) + { + if (comp_size > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + MZ_WRITE_LE32(local_dir_footer + 8, comp_size); + MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size); + } + else + { + MZ_WRITE_LE64(local_dir_footer + 8, comp_size); + MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size); + local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64; + } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size) + return MZ_FALSE; + + cur_archive_file_ofs += local_dir_footer_size; + } + + if (pExtra_data != NULL) + { + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, extra_size, pComment, + comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes, + user_extra_data_central, user_extra_data_central_len)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 size_to_add, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) +{ + mz_uint16 gen_flags = MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR; + mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes; + mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0; + mz_uint64 local_dir_header_ofs, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = size_to_add, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + mz_uint8 *pExtra_data = NULL; + mz_uint32 extra_size = 0; + mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE]; + mz_zip_internal_state *pState; + + if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME)) + gen_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + + /* Sanity checks */ + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + if ((!pState->m_zip64) && (uncomp_size > MZ_UINT32_MAX)) + { + /* Source file is too large for non-zip64 */ + /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ + pState->m_zip64 = MZ_TRUE; + } + + /* We could support this, but why? */ + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); + + if (pState->m_zip64) + { + if (pZip->m_total_files == MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + if (pZip->m_total_files == MZ_UINT16_MAX) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */ + } + } + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ + if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + if (!pState->m_zip64) + { + /* Bail early if the archive would obviously become too large */ + if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + archive_name_size + comment_size + user_extra_data_len + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 1024 + + MZ_ZIP_DATA_DESCRIPTER_SIZE32 + user_extra_data_central_len) > 0xFFFFFFFF) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ + } + } + +#ifndef MINIZ_NO_TIME + if (pFile_time) + { + mz_zip_time_t_to_dos_time(*pFile_time, &dos_time, &dos_date); + } +#endif + + if (uncomp_size <= 3) + level = 0; + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes)) + { + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_file_ofs += num_alignment_padding_bytes; + local_dir_header_ofs = cur_archive_file_ofs; + + if (pZip->m_file_offset_alignment) + { + MZ_ASSERT((cur_archive_file_ofs & (pZip->m_file_offset_alignment - 1)) == 0); + } + + if (uncomp_size && level) + { + method = MZ_DEFLATED; + } + + MZ_CLEAR_OBJ(local_dir_header); + if (pState->m_zip64) + { + if (uncomp_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX) + { + pExtra_data = extra_data; + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, extra_size + user_extra_data_len, 0, 0, 0, method, gen_flags, dos_time, dos_date)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_file_ofs += archive_name_size; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += extra_size; + } + else + { + if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, user_extra_data_len, 0, 0, 0, method, gen_flags, dos_time, dos_date)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_file_ofs += archive_name_size; + } + + if (user_extra_data_len > 0) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += user_extra_data_len; + } + + if (uncomp_size) + { + mz_uint64 uncomp_remaining = uncomp_size; + void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE); + if (!pRead_buf) + { + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!level) + { + while (uncomp_remaining) + { + mz_uint n = (mz_uint)MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, uncomp_remaining); + if ((MZ_FREAD(pRead_buf, 1, n, pSrc_file) != n) || (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); + uncomp_remaining -= n; + cur_archive_file_ofs += n; + } + comp_size = uncomp_size; + } + else + { + mz_bool result = MZ_FALSE; + mz_zip_writer_add_state state; + tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + } + + for (;;) + { + size_t in_buf_size = (mz_uint32)MZ_MIN(uncomp_remaining, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); + tdefl_status status; + tdefl_flush flush = TDEFL_NO_FLUSH; + + if (MZ_FREAD(pRead_buf, 1, in_buf_size, pSrc_file) != in_buf_size) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + break; + } + + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, in_buf_size); + uncomp_remaining -= in_buf_size; + + if (pZip->m_pNeeds_keepalive != NULL && pZip->m_pNeeds_keepalive(pZip->m_pIO_opaque)) + flush = TDEFL_FULL_FLUSH; + + status = tdefl_compress_buffer(pComp, pRead_buf, in_buf_size, uncomp_remaining ? flush : TDEFL_FINISH); + if (status == TDEFL_STATUS_DONE) + { + result = MZ_TRUE; + break; + } + else if (status != TDEFL_STATUS_OKAY) + { + mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED); + break; + } + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + + if (!result) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return MZ_FALSE; + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + } + + { + mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64]; + mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32; + + MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID); + MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32); + if (pExtra_data == NULL) + { + if (comp_size > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + MZ_WRITE_LE32(local_dir_footer + 8, comp_size); + MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size); + } + else + { + MZ_WRITE_LE64(local_dir_footer + 8, comp_size); + MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size); + local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64; + } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size) + return MZ_FALSE; + + cur_archive_file_ofs += local_dir_footer_size; + } + + if (pExtra_data != NULL) + { + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, extra_size, pComment, comment_size, + uncomp_size, comp_size, uncomp_crc32, method, gen_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes, + user_extra_data_central, user_extra_data_central_len)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) +{ + MZ_FILE *pSrc_file = NULL; + mz_uint64 uncomp_size = 0; + MZ_TIME_T file_modified_time; + MZ_TIME_T *pFile_time = NULL; + mz_bool status; + + memset(&file_modified_time, 0, sizeof(file_modified_time)); + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO) + pFile_time = &file_modified_time; + if (!mz_zip_get_file_modified_time(pSrc_filename, &file_modified_time)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_STAT_FAILED); +#endif + + pSrc_file = MZ_FOPEN(pSrc_filename, "rb"); + if (!pSrc_file) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + + MZ_FSEEK64(pSrc_file, 0, SEEK_END); + uncomp_size = MZ_FTELL64(pSrc_file); + MZ_FSEEK64(pSrc_file, 0, SEEK_SET); + + status = mz_zip_writer_add_cfile(pZip, pArchive_name, pSrc_file, uncomp_size, pFile_time, pComment, comment_size, level_and_flags, NULL, 0, NULL, 0); + + MZ_FCLOSE(pSrc_file); + + return status; +} +#endif /* #ifndef MINIZ_NO_STDIO */ + +static mz_bool mz_zip_writer_update_zip64_extension_block(mz_zip_array *pNew_ext, mz_zip_archive *pZip, const mz_uint8 *pExt, uint32_t ext_len, mz_uint64 *pComp_size, mz_uint64 *pUncomp_size, mz_uint64 *pLocal_header_ofs, mz_uint32 *pDisk_start) +{ + /* + 64 should be enough for any new zip64 data */ + if (!mz_zip_array_reserve(pZip, pNew_ext, ext_len + 64, MZ_FALSE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + mz_zip_array_resize(pZip, pNew_ext, 0, MZ_FALSE); + + if ((pUncomp_size) || (pComp_size) || (pLocal_header_ofs) || (pDisk_start)) + { + mz_uint8 new_ext_block[64]; + mz_uint8 *pDst = new_ext_block; + mz_write_le16(pDst, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID); + mz_write_le16(pDst + sizeof(mz_uint16), 0); + pDst += sizeof(mz_uint16) * 2; + + if (pUncomp_size) + { + mz_write_le64(pDst, *pUncomp_size); + pDst += sizeof(mz_uint64); + } + + if (pComp_size) + { + mz_write_le64(pDst, *pComp_size); + pDst += sizeof(mz_uint64); + } + + if (pLocal_header_ofs) + { + mz_write_le64(pDst, *pLocal_header_ofs); + pDst += sizeof(mz_uint64); + } + + if (pDisk_start) + { + mz_write_le32(pDst, *pDisk_start); + pDst += sizeof(mz_uint32); + } + + mz_write_le16(new_ext_block + sizeof(mz_uint16), (mz_uint16)((pDst - new_ext_block) - sizeof(mz_uint16) * 2)); + + if (!mz_zip_array_push_back(pZip, pNew_ext, new_ext_block, pDst - new_ext_block)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if ((pExt) && (ext_len)) + { + mz_uint32 extra_size_remaining = ext_len; + const mz_uint8 *pExtra_data = pExt; + + do + { + mz_uint32 field_id, field_data_size, field_total_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + field_total_size = field_data_size + sizeof(mz_uint16) * 2; + + if (field_total_size > extra_size_remaining) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (field_id != MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + if (!mz_zip_array_push_back(pZip, pNew_ext, pExtra_data, field_total_size)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + pExtra_data += field_total_size; + extra_size_remaining -= field_total_size; + } while (extra_size_remaining); + } + + return MZ_TRUE; +} + +/* TODO: This func is now pretty freakin complex due to zip64, split it up? */ +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index) +{ + mz_uint n, bit_flags, num_alignment_padding_bytes, src_central_dir_following_data_size; + mz_uint64 src_archive_bytes_remaining, local_dir_header_ofs; + mz_uint64 cur_src_file_ofs, cur_dst_file_ofs; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + mz_uint8 new_central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + size_t orig_central_dir_size; + mz_zip_internal_state *pState; + void *pBuf; + const mz_uint8 *pSrc_central_header; + mz_zip_archive_file_stat src_file_stat; + mz_uint32 src_filename_len, src_comment_len, src_ext_len; + mz_uint32 local_header_filename_size, local_header_extra_len; + mz_uint64 local_header_comp_size, local_header_uncomp_size; + mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE; + + /* Sanity checks */ + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pSource_zip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + /* Don't support copying files from zip64 archives to non-zip64, even though in some cases this is possible */ + if ((pSource_zip->m_pState->m_zip64) && (!pZip->m_pState->m_zip64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Get pointer to the source central dir header and crack it */ + if (NULL == (pSrc_central_header = mz_zip_get_cdh(pSource_zip, src_file_index))) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_SIG_OFS) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + src_filename_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS); + src_comment_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS); + src_ext_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS); + src_central_dir_following_data_size = src_filename_len + src_ext_len + src_comment_len; + + /* TODO: We don't support central dir's >= MZ_UINT32_MAX bytes right now (+32 fudge factor in case we need to add more extra data) */ + if ((pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + 32) >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + if (!pState->m_zip64) + { + if (pZip->m_total_files == MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + /* TODO: Our zip64 support still has some 32-bit limits that may not be worth fixing. */ + if (pZip->m_total_files == MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + + if (!mz_zip_file_stat_internal(pSource_zip, src_file_index, pSrc_central_header, &src_file_stat, NULL)) + return MZ_FALSE; + + cur_src_file_ofs = src_file_stat.m_local_header_ofs; + cur_dst_file_ofs = pZip->m_archive_size; + + /* Read the source archive's local dir header */ + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + /* Compute the total size we need to copy (filename+extra data+compressed data) */ + local_header_filename_size = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS); + local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS); + local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS); + src_archive_bytes_remaining = local_header_filename_size + local_header_extra_len + src_file_stat.m_comp_size; + + /* Try to find a zip64 extended information field */ + if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX))) + { + mz_zip_array file_data_array; + const mz_uint8 *pExtra_data; + mz_uint32 extra_size_remaining = local_header_extra_len; + + mz_zip_array_init(&file_data_array, 1); + if (!mz_zip_array_resize(pZip, &file_data_array, local_header_extra_len, MZ_FALSE)) + { + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, src_file_stat.m_local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_size, file_data_array.m_p, local_header_extra_len) != local_header_extra_len) + { + mz_zip_array_clear(pZip, &file_data_array); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + pExtra_data = (const mz_uint8 *)file_data_array.m_p; + + do + { + mz_uint32 field_id, field_data_size, field_total_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + { + mz_zip_array_clear(pZip, &file_data_array); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + field_total_size = field_data_size + sizeof(mz_uint16) * 2; + + if (field_total_size > extra_size_remaining) + { + mz_zip_array_clear(pZip, &file_data_array); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32); + + if (field_data_size < sizeof(mz_uint64) * 2) + { + mz_zip_array_clear(pZip, &file_data_array); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data); + local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64)); /* may be 0 if there's a descriptor */ + + found_zip64_ext_data_in_ldir = MZ_TRUE; + break; + } + + pExtra_data += field_total_size; + extra_size_remaining -= field_total_size; + } while (extra_size_remaining); + + mz_zip_array_clear(pZip, &file_data_array); + } + + if (!pState->m_zip64) + { + /* Try to detect if the new archive will most likely wind up too big and bail early (+(sizeof(mz_uint32) * 4) is for the optional descriptor which could be present, +64 is a fudge factor). */ + /* We also check when the archive is finalized so this doesn't need to be perfect. */ + mz_uint64 approx_new_archive_size = cur_dst_file_ofs + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + src_archive_bytes_remaining + (sizeof(mz_uint32) * 4) + + pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 64; + + if (approx_new_archive_size >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + } + + /* Write dest archive padding */ + if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes)) + return MZ_FALSE; + + cur_dst_file_ofs += num_alignment_padding_bytes; + + local_dir_header_ofs = cur_dst_file_ofs; + if (pZip->m_file_offset_alignment) + { + MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); + } + + /* The original zip's local header+ext block doesn't change, even with zip64, so we can just copy it over to the dest zip */ + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + /* Copy over the source archive bytes to the dest archive, also ensure we have enough buf space to handle optional data descriptor */ + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)MZ_MAX(32U, MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining))))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + while (src_archive_bytes_remaining) + { + n = (mz_uint)MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining); + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + cur_src_file_ofs += n; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + cur_dst_file_ofs += n; + + src_archive_bytes_remaining -= n; + } + + /* Now deal with the optional data descriptor */ + bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); + if (bit_flags & 8) + { + /* Copy data descriptor */ + if ((pSource_zip->m_pState->m_zip64) || (found_zip64_ext_data_in_ldir)) + { + /* src is zip64, dest must be zip64 */ + + /* name uint32_t's */ + /* id 1 (optional in zip64?) */ + /* crc 1 */ + /* comp_size 2 */ + /* uncomp_size 2 */ + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, (sizeof(mz_uint32) * 6)) != (sizeof(mz_uint32) * 6)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID) ? 6 : 5); + } + else + { + /* src is NOT zip64 */ + mz_bool has_id; + + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + has_id = (MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID); + + if (pZip->m_pState->m_zip64) + { + /* dest is zip64, so upgrade the data descriptor */ + const mz_uint32 *pSrc_descriptor = (const mz_uint32 *)((const mz_uint8 *)pBuf + (has_id ? sizeof(mz_uint32) : 0)); + const mz_uint32 src_crc32 = pSrc_descriptor[0]; + const mz_uint64 src_comp_size = pSrc_descriptor[1]; + const mz_uint64 src_uncomp_size = pSrc_descriptor[2]; + + mz_write_le32((mz_uint8 *)pBuf, MZ_ZIP_DATA_DESCRIPTOR_ID); + mz_write_le32((mz_uint8 *)pBuf + sizeof(mz_uint32) * 1, src_crc32); + mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 2, src_comp_size); + mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 4, src_uncomp_size); + + n = sizeof(mz_uint32) * 6; + } + else + { + /* dest is NOT zip64, just copy it as-is */ + n = sizeof(mz_uint32) * (has_id ? 4 : 3); + } + } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_src_file_ofs += n; + cur_dst_file_ofs += n; + } + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + + /* Finally, add the new central dir header */ + orig_central_dir_size = pState->m_central_dir.m_size; + + memcpy(new_central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + + if (pState->m_zip64) + { + /* This is the painful part: We need to write a new central dir header + ext block with updated zip64 fields, and ensure the old fields (if any) are not included. */ + const mz_uint8 *pSrc_ext = pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len; + mz_zip_array new_ext_block; + + mz_zip_array_init(&new_ext_block, sizeof(mz_uint8)); + + MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_UINT32_MAX); + MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_UINT32_MAX); + MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_UINT32_MAX); + + if (!mz_zip_writer_update_zip64_extension_block(&new_ext_block, pZip, pSrc_ext, src_ext_len, &src_file_stat.m_comp_size, &src_file_stat.m_uncomp_size, &local_dir_header_ofs, NULL)) + { + mz_zip_array_clear(pZip, &new_ext_block); + return MZ_FALSE; + } + + MZ_WRITE_LE16(new_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS, new_ext_block.m_size); + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) + { + mz_zip_array_clear(pZip, &new_ext_block); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_filename_len)) + { + mz_zip_array_clear(pZip, &new_ext_block); + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_ext_block.m_p, new_ext_block.m_size)) + { + mz_zip_array_clear(pZip, &new_ext_block); + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len + src_ext_len, src_comment_len)) + { + mz_zip_array_clear(pZip, &new_ext_block); + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + mz_zip_array_clear(pZip, &new_ext_block); + } + else + { + /* sanity checks */ + if (cur_dst_file_ofs > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + if (local_dir_header_ofs >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs); + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_central_dir_following_data_size)) + { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + } + + /* This shouldn't trigger unless we screwed up during the initial sanity checks */ + if (pState->m_central_dir.m_size >= MZ_UINT32_MAX) + { + /* TODO: Support central dirs >= 32-bits in size */ + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + } + + n = (mz_uint32)orig_central_dir_size; + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1)) + { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + pZip->m_total_files++; + pZip->m_archive_size = cur_dst_file_ofs; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState; + mz_uint64 central_dir_ofs, central_dir_size; + mz_uint8 hdr[256]; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + if (pState->m_zip64) + { + if ((pZip->m_total_files > MZ_UINT32_MAX) || (pState->m_central_dir.m_size >= MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + if ((pZip->m_total_files > MZ_UINT16_MAX) || ((pZip->m_archive_size + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + + central_dir_ofs = 0; + central_dir_size = 0; + if (pZip->m_total_files) + { + /* Write central directory */ + central_dir_ofs = pZip->m_archive_size; + central_dir_size = pState->m_central_dir.m_size; + pZip->m_central_directory_file_ofs = central_dir_ofs; + if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, (size_t)central_dir_size) != central_dir_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + pZip->m_archive_size += central_dir_size; + } + + if (pState->m_zip64) + { + /* Write zip64 end of central directory header */ + mz_uint64 rel_ofs_to_zip64_ecdr = pZip->m_archive_size; + + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDH_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - sizeof(mz_uint32) - sizeof(mz_uint64)); + MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS, 0x031E); /* TODO: always Unix */ + MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS, 0x002D); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_SIZE_OFS, central_dir_size); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_OFS_OFS, central_dir_ofs); + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE; + + /* Write zip64 end of central directory locator */ + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS, rel_ofs_to_zip64_ecdr); + MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS, 1); + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE; + } + + /* Write end of central directory record */ + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files)); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files)); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_size)); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_ofs)); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + +#ifndef MINIZ_NO_STDIO + if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); +#endif /* #ifndef MINIZ_NO_STDIO */ + + pZip->m_archive_size += MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE; + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize) +{ + if ((!ppBuf) || (!pSize)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + *ppBuf = NULL; + *pSize = 0; + + if ((!pZip) || (!pZip->m_pState)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (pZip->m_pWrite != mz_zip_heap_write_func) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_writer_finalize_archive(pZip)) + return MZ_FALSE; + + *ppBuf = pZip->m_pState->m_pMem; + *pSize = pZip->m_pState->m_mem_size; + pZip->m_pState->m_pMem = NULL; + pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_end(mz_zip_archive *pZip) +{ + return mz_zip_writer_end_internal(pZip, MZ_TRUE); +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) +{ + return mz_zip_add_mem_to_archive_file_in_place_v2(pZip_filename, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, NULL); +} + +mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr) +{ + mz_bool status, created_new_archive = MZ_FALSE; + mz_zip_archive zip_archive; + struct MZ_FILE_STAT_STRUCT file_stat; + mz_zip_error actual_err = MZ_ZIP_NO_ERROR; + + mz_zip_zero_struct(&zip_archive); + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + + if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION)) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_PARAMETER; + return MZ_FALSE; + } + + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_FILENAME; + return MZ_FALSE; + } + + /* Important: The regular non-64 bit version of stat() can fail here if the file is very large, which could cause the archive to be overwritten. */ + /* So be sure to compile with _LARGEFILE64_SOURCE 1 */ + if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0) + { + /* Create a new archive. */ + if (!mz_zip_writer_init_file_v2(&zip_archive, pZip_filename, 0, level_and_flags)) + { + if (pErr) + *pErr = zip_archive.m_last_error; + return MZ_FALSE; + } + + created_new_archive = MZ_TRUE; + } + else + { + /* Append to an existing archive. */ + if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0)) + { + if (pErr) + *pErr = zip_archive.m_last_error; + return MZ_FALSE; + } + + if (!mz_zip_writer_init_from_reader_v2(&zip_archive, pZip_filename, level_and_flags)) + { + if (pErr) + *pErr = zip_archive.m_last_error; + + mz_zip_reader_end_internal(&zip_archive, MZ_FALSE); + + return MZ_FALSE; + } + } + + status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0); + actual_err = zip_archive.m_last_error; + + /* Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.) */ + if (!mz_zip_writer_finalize_archive(&zip_archive)) + { + if (!actual_err) + actual_err = zip_archive.m_last_error; + + status = MZ_FALSE; + } + + if (!mz_zip_writer_end_internal(&zip_archive, status)) + { + if (!actual_err) + actual_err = zip_archive.m_last_error; + + status = MZ_FALSE; + } + + if ((!status) && (created_new_archive)) + { + /* It's a new archive and something went wrong, so just delete it. */ + int ignoredStatus = MZ_DELETE_FILE(pZip_filename); + (void)ignoredStatus; + } + + if (pErr) + *pErr = actual_err; + + return status; +} + +void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr) +{ + mz_uint32 file_index; + mz_zip_archive zip_archive; + void *p = NULL; + + if (pSize) + *pSize = 0; + + if ((!pZip_filename) || (!pArchive_name)) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_PARAMETER; + + return NULL; + } + + mz_zip_zero_struct(&zip_archive); + if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0)) + { + if (pErr) + *pErr = zip_archive.m_last_error; + + return NULL; + } + + if (mz_zip_reader_locate_file_v2(&zip_archive, pArchive_name, pComment, flags, &file_index)) + { + p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags); + } + + mz_zip_reader_end_internal(&zip_archive, p != NULL); + + if (pErr) + *pErr = zip_archive.m_last_error; + + return p; +} + +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags) +{ + return mz_zip_extract_archive_file_to_heap_v2(pZip_filename, pArchive_name, NULL, pSize, flags, NULL); +} + +#endif /* #ifndef MINIZ_NO_STDIO */ + +#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */ + +/* ------------------- Misc utils */ + +mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_zip_mode : MZ_ZIP_MODE_INVALID; +} + +mz_zip_type mz_zip_get_type(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_zip_type : MZ_ZIP_TYPE_INVALID; +} + +mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num) +{ + mz_zip_error prev_err; + + if (!pZip) + return MZ_ZIP_INVALID_PARAMETER; + + prev_err = pZip->m_last_error; + + pZip->m_last_error = err_num; + return prev_err; +} + +mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip) +{ + if (!pZip) + return MZ_ZIP_INVALID_PARAMETER; + + return pZip->m_last_error; +} + +mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip) +{ + return mz_zip_set_last_error(pZip, MZ_ZIP_NO_ERROR); +} + +mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip) +{ + mz_zip_error prev_err; + + if (!pZip) + return MZ_ZIP_INVALID_PARAMETER; + + prev_err = pZip->m_last_error; + + pZip->m_last_error = MZ_ZIP_NO_ERROR; + return prev_err; +} + +const char *mz_zip_get_error_string(mz_zip_error mz_err) +{ + switch (mz_err) + { + case MZ_ZIP_NO_ERROR: + return "no error"; + case MZ_ZIP_UNDEFINED_ERROR: + return "undefined error"; + case MZ_ZIP_TOO_MANY_FILES: + return "too many files"; + case MZ_ZIP_FILE_TOO_LARGE: + return "file too large"; + case MZ_ZIP_UNSUPPORTED_METHOD: + return "unsupported method"; + case MZ_ZIP_UNSUPPORTED_ENCRYPTION: + return "unsupported encryption"; + case MZ_ZIP_UNSUPPORTED_FEATURE: + return "unsupported feature"; + case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR: + return "failed finding central directory"; + case MZ_ZIP_NOT_AN_ARCHIVE: + return "not a ZIP archive"; + case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED: + return "invalid header or archive is corrupted"; + case MZ_ZIP_UNSUPPORTED_MULTIDISK: + return "unsupported multidisk archive"; + case MZ_ZIP_DECOMPRESSION_FAILED: + return "decompression failed or archive is corrupted"; + case MZ_ZIP_COMPRESSION_FAILED: + return "compression failed"; + case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE: + return "unexpected decompressed size"; + case MZ_ZIP_CRC_CHECK_FAILED: + return "CRC-32 check failed"; + case MZ_ZIP_UNSUPPORTED_CDIR_SIZE: + return "unsupported central directory size"; + case MZ_ZIP_ALLOC_FAILED: + return "allocation failed"; + case MZ_ZIP_FILE_OPEN_FAILED: + return "file open failed"; + case MZ_ZIP_FILE_CREATE_FAILED: + return "file create failed"; + case MZ_ZIP_FILE_WRITE_FAILED: + return "file write failed"; + case MZ_ZIP_FILE_READ_FAILED: + return "file read failed"; + case MZ_ZIP_FILE_CLOSE_FAILED: + return "file close failed"; + case MZ_ZIP_FILE_SEEK_FAILED: + return "file seek failed"; + case MZ_ZIP_FILE_STAT_FAILED: + return "file stat failed"; + case MZ_ZIP_INVALID_PARAMETER: + return "invalid parameter"; + case MZ_ZIP_INVALID_FILENAME: + return "invalid filename"; + case MZ_ZIP_BUF_TOO_SMALL: + return "buffer too small"; + case MZ_ZIP_INTERNAL_ERROR: + return "internal error"; + case MZ_ZIP_FILE_NOT_FOUND: + return "file not found"; + case MZ_ZIP_ARCHIVE_TOO_LARGE: + return "archive is too large"; + case MZ_ZIP_VALIDATION_FAILED: + return "validation failed"; + case MZ_ZIP_WRITE_CALLBACK_FAILED: + return "write calledback failed"; + default: + break; + } + + return "unknown error"; +} + +/* Note: Just because the archive is not zip64 doesn't necessarily mean it doesn't have Zip64 extended information extra field, argh. */ +mz_bool mz_zip_is_zip64(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return MZ_FALSE; + + return pZip->m_pState->m_zip64; +} + +size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return 0; + + return pZip->m_pState->m_central_dir.m_size; +} + +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_total_files : 0; +} + +mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip) +{ + if (!pZip) + return 0; + return pZip->m_archive_size; +} + +mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return 0; + return pZip->m_pState->m_file_archive_start_ofs; +} + +MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return 0; + return pZip->m_pState->m_pFile; +} + +size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + return pZip->m_pRead(pZip->m_pIO_opaque, file_ofs, pBuf, n); +} + +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size) +{ + mz_uint n; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + if (filename_buf_size) + pFilename[0] = '\0'; + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return 0; + } + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_buf_size) + { + n = MZ_MIN(n, filename_buf_size - 1); + memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pFilename[n] = '\0'; + } + return n + 1; +} + +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat) +{ + return mz_zip_file_stat_internal(pZip, file_index, mz_zip_get_cdh(pZip, file_index), pStat, NULL); +} + +mz_bool mz_zip_end(mz_zip_archive *pZip) +{ + if (!pZip) + return MZ_FALSE; + + if (pZip->m_zip_mode == MZ_ZIP_MODE_READING) + return mz_zip_reader_end(pZip); +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + else if ((pZip->m_zip_mode == MZ_ZIP_MODE_WRITING) || (pZip->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED)) + return mz_zip_writer_end(pZip); +#endif + + return MZ_FALSE; +} + +#ifdef __cplusplus +} +#endif + +#endif /*#ifndef MINIZ_NO_ARCHIVE_APIS*/ diff --git a/include/miniz/miniz.h b/include/miniz/miniz.h new file mode 100755 index 0000000..67c9e55 --- /dev/null +++ b/include/miniz/miniz.h @@ -0,0 +1,1321 @@ +/* miniz.c 2.0.8 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing + See "unlicense" statement at the end of this file. + Rich Geldreich , last updated Oct. 13, 2013 + Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt + + Most API's defined in miniz.c are optional. For example, to disable the archive related functions just define + MINIZ_NO_ARCHIVE_APIS, or to get rid of all stdio usage define MINIZ_NO_STDIO (see the list below for more macros). + + * Low-level Deflate/Inflate implementation notes: + + Compression: Use the "tdefl" API's. The compressor supports raw, static, and dynamic blocks, lazy or + greedy parsing, match length filtering, RLE-only, and Huffman-only streams. It performs and compresses + approximately as well as zlib. + + Decompression: Use the "tinfl" API's. The entire decompressor is implemented as a single function + coroutine: see tinfl_decompress(). It supports decompression into a 32KB (or larger power of 2) wrapping buffer, or into a memory + block large enough to hold the entire file. + + The low-level tdefl/tinfl API's do not make any use of dynamic memory allocation. + + * zlib-style API notes: + + miniz.c implements a fairly large subset of zlib. There's enough functionality present for it to be a drop-in + zlib replacement in many apps: + The z_stream struct, optional memory allocation callbacks + deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound + inflateInit/inflateInit2/inflate/inflateEnd + compress, compress2, compressBound, uncompress + CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly routines. + Supports raw deflate streams or standard zlib streams with adler-32 checking. + + Limitations: + The callback API's are not implemented yet. No support for gzip headers or zlib static dictionaries. + I've tried to closely emulate zlib's various flavors of stream flushing and return status codes, but + there are no guarantees that miniz.c pulls this off perfectly. + + * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, originally written by + Alex Evans. Supports 1-4 bytes/pixel images. + + * ZIP archive API notes: + + The ZIP archive API's where designed with simplicity and efficiency in mind, with just enough abstraction to + get the job done with minimal fuss. There are simple API's to retrieve file information, read files from + existing archives, create new archives, append new files to existing archives, or clone archive data from + one archive to another. It supports archives located in memory or the heap, on disk (using stdio.h), + or you can specify custom file read/write callbacks. + + - Archive reading: Just call this function to read a single file from a disk archive: + + void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, + size_t *pSize, mz_uint zip_flags); + + For more complex cases, use the "mz_zip_reader" functions. Upon opening an archive, the entire central + directory is located and read as-is into memory, and subsequent file access only occurs when reading individual files. + + - Archives file scanning: The simple way is to use this function to scan a loaded archive for a specific file: + + int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); + + The locate operation can optionally check file comments too, which (as one example) can be used to identify + multiple versions of the same file in an archive. This function uses a simple linear search through the central + directory, so it's not very fast. + + Alternately, you can iterate through all the files in an archive (using mz_zip_reader_get_num_files()) and + retrieve detailed info on each file by calling mz_zip_reader_file_stat(). + + - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer immediately writes compressed file data + to disk and builds an exact image of the central directory in memory. The central directory image is written + all at once at the end of the archive file when the archive is finalized. + + The archive writer can optionally align each file's local header and file data to any power of 2 alignment, + which can be useful when the archive will be read from optical media. Also, the writer supports placing + arbitrary data blobs at the very beginning of ZIP archives. Archives written using either feature are still + readable by any ZIP tool. + + - Archive appending: The simple way to add a single file to an archive is to call this function: + + mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, + const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + + The archive will be created if it doesn't already exist, otherwise it'll be appended to. + Note the appending is done in-place and is not an atomic operation, so if something goes wrong + during the operation it's possible the archive could be left without a central directory (although the local + file headers and file data will be fine, so the archive will be recoverable). + + For more complex archive modification scenarios: + 1. The safest way is to use a mz_zip_reader to read the existing archive, cloning only those bits you want to + preserve into a new archive using using the mz_zip_writer_add_from_zip_reader() function (which compiles the + compressed file data as-is). When you're done, delete the old archive and rename the newly written archive, and + you're done. This is safe but requires a bunch of temporary disk space or heap memory. + + 2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using mz_zip_writer_init_from_reader(), + append new files as needed, then finalize the archive which will write an updated central directory to the + original archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() does.) There's a + possibility that the archive's central directory could be lost with this method if anything goes wrong, though. + + - ZIP archive support limitations: + No zip64 or spanning support. Extraction functions can only handle unencrypted, stored or deflated files. + Requires streams capable of seeking. + + * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the + below header, or create miniz.h, #define MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it. + + * Important: For best perf. be sure to customize the below macros for your target platform: + #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 + #define MINIZ_LITTLE_ENDIAN 1 + #define MINIZ_HAS_64BIT_REGISTERS 1 + + * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before including miniz.c to ensure miniz + uses the 64-bit variants: fopen64(), stat64(), etc. Otherwise you won't be able to process large files + (i.e. 32-bit stat() fails for me on files > 0x7FFFFFFF bytes). +*/ +#pragma once + + + + + +/* Defines to completely disable specific portions of miniz.c: + If all macros here are defined the only functionality remaining will be CRC-32, adler-32, tinfl, and tdefl. */ + +/* Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. */ +/*#define MINIZ_NO_STDIO */ + +/* If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or */ +/* get/set file times, and the C run-time funcs that get/set times won't be called. */ +/* The current downside is the times written to your archives will be from 1979. */ +/*#define MINIZ_NO_TIME */ + +/* Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. */ +/*#define MINIZ_NO_ARCHIVE_APIS */ + +/* Define MINIZ_NO_ARCHIVE_WRITING_APIS to disable all writing related ZIP archive API's. */ +/*#define MINIZ_NO_ARCHIVE_WRITING_APIS */ + +/* Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression API's. */ +/*#define MINIZ_NO_ZLIB_APIS */ + +/* Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib. */ +/*#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES */ + +/* Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. + Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc + callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user + functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. */ +/*#define MINIZ_NO_MALLOC */ + +#if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) +/* TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux */ +#define MINIZ_NO_TIME +#endif + +#include + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS) +#include +#endif + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__) +/* MINIZ_X86_OR_X64_CPU is only used to help set the below macros. */ +#define MINIZ_X86_OR_X64_CPU 1 +#else +#define MINIZ_X86_OR_X64_CPU 0 +#endif + +#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU +/* Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. */ +#define MINIZ_LITTLE_ENDIAN 1 +#else +#define MINIZ_LITTLE_ENDIAN 0 +#endif + +#if MINIZ_X86_OR_X64_CPU +/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. */ +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 +#else +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 +#endif + +#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__) +/* Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). */ +#define MINIZ_HAS_64BIT_REGISTERS 1 +#else +#define MINIZ_HAS_64BIT_REGISTERS 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- zlib-style API Definitions. */ + +/* For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! */ +typedef unsigned long mz_ulong; + +/* mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. */ +void mz_free(void *p); + +#define MZ_ADLER32_INIT (1) +/* mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. */ +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); + +#define MZ_CRC32_INIT (0) +/* mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. */ +mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); + +/* Compression strategies. */ +enum +{ + MZ_DEFAULT_STRATEGY = 0, + MZ_FILTERED = 1, + MZ_HUFFMAN_ONLY = 2, + MZ_RLE = 3, + MZ_FIXED = 4 +}; + +/* Method */ +#define MZ_DEFLATED 8 + +/* Heap allocation callbacks. +Note that mz_alloc_func parameter types purpsosely differ from zlib's: items/size is size_t, not unsigned long. */ +typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); +typedef void (*mz_free_func)(void *opaque, void *address); +typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size); + +/* Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. */ +enum +{ + MZ_NO_COMPRESSION = 0, + MZ_BEST_SPEED = 1, + MZ_BEST_COMPRESSION = 9, + MZ_UBER_COMPRESSION = 10, + MZ_DEFAULT_LEVEL = 6, + MZ_DEFAULT_COMPRESSION = -1 +}; + +#define MZ_VERSION "10.0.3" +#define MZ_VERNUM 0xA030 +#define MZ_VER_MAJOR 10 +#define MZ_VER_MINOR 0 +#define MZ_VER_REVISION 3 +#define MZ_VER_SUBREVISION 0 + +#ifndef MINIZ_NO_ZLIB_APIS + +/* Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs). */ +enum +{ + MZ_NO_FLUSH = 0, + MZ_PARTIAL_FLUSH = 1, + MZ_SYNC_FLUSH = 2, + MZ_FULL_FLUSH = 3, + MZ_FINISH = 4, + MZ_BLOCK = 5 +}; + +/* Return status codes. MZ_PARAM_ERROR is non-standard. */ +enum +{ + MZ_OK = 0, + MZ_STREAM_END = 1, + MZ_NEED_DICT = 2, + MZ_ERRNO = -1, + MZ_STREAM_ERROR = -2, + MZ_DATA_ERROR = -3, + MZ_MEM_ERROR = -4, + MZ_BUF_ERROR = -5, + MZ_VERSION_ERROR = -6, + MZ_PARAM_ERROR = -10000 +}; + +/* Window bits */ +#define MZ_DEFAULT_WINDOW_BITS 15 + +struct mz_internal_state; + +/* Compression/decompression stream struct. */ +typedef struct mz_stream_s +{ + const unsigned char *next_in; /* pointer to next byte to read */ + unsigned int avail_in; /* number of bytes available at next_in */ + mz_ulong total_in; /* total number of bytes consumed so far */ + + unsigned char *next_out; /* pointer to next byte to write */ + unsigned int avail_out; /* number of bytes that can be written to next_out */ + mz_ulong total_out; /* total number of bytes produced so far */ + + char *msg; /* error msg (unused) */ + struct mz_internal_state *state; /* internal state, allocated by zalloc/zfree */ + + mz_alloc_func zalloc; /* optional heap allocation function (defaults to malloc) */ + mz_free_func zfree; /* optional heap free function (defaults to free) */ + void *opaque; /* heap alloc function user pointer */ + + int data_type; /* data_type (unused) */ + mz_ulong adler; /* adler32 of the source or uncompressed data */ + mz_ulong reserved; /* not used */ +} mz_stream; + +typedef mz_stream *mz_streamp; + +/* Returns the version string of miniz.c. */ +const char *mz_version(void); + +/* mz_deflateInit() initializes a compressor with default options: */ +/* Parameters: */ +/* pStream must point to an initialized mz_stream struct. */ +/* level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. */ +/* level 1 enables a specially optimized compression function that's been optimized purely for performance, not ratio. */ +/* (This special func. is currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and MINIZ_LITTLE_ENDIAN are defined.) */ +/* Return values: */ +/* MZ_OK on success. */ +/* MZ_STREAM_ERROR if the stream is bogus. */ +/* MZ_PARAM_ERROR if the input parameters are bogus. */ +/* MZ_MEM_ERROR on out of memory. */ +int mz_deflateInit(mz_streamp pStream, int level); + +/* mz_deflateInit2() is like mz_deflate(), except with more control: */ +/* Additional parameters: */ +/* method must be MZ_DEFLATED */ +/* window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) */ +/* mem_level must be between [1, 9] (it's checked but ignored by miniz.c) */ +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); + +/* Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). */ +int mz_deflateReset(mz_streamp pStream); + +/* mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. */ +/* Parameters: */ +/* pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */ +/* flush may be MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. */ +/* Return values: */ +/* MZ_OK on success (when flushing, or if more input is needed but not available, and/or there's more output to be written but the output buffer is full). */ +/* MZ_STREAM_END if all input has been consumed and all output bytes have been written. Don't call mz_deflate() on the stream anymore. */ +/* MZ_STREAM_ERROR if the stream is bogus. */ +/* MZ_PARAM_ERROR if one of the parameters is invalid. */ +/* MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.) */ +int mz_deflate(mz_streamp pStream, int flush); + +/* mz_deflateEnd() deinitializes a compressor: */ +/* Return values: */ +/* MZ_OK on success. */ +/* MZ_STREAM_ERROR if the stream is bogus. */ +int mz_deflateEnd(mz_streamp pStream); + +/* mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH. */ +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); + +/* Single-call compression functions mz_compress() and mz_compress2(): */ +/* Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. */ +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); + +/* mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). */ +mz_ulong mz_compressBound(mz_ulong source_len); + +/* Initializes a decompressor. */ +int mz_inflateInit(mz_streamp pStream); + +/* mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer: */ +/* window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). */ +int mz_inflateInit2(mz_streamp pStream, int window_bits); + +/* Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible. */ +/* Parameters: */ +/* pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */ +/* flush may be MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. */ +/* On the first call, if flush is MZ_FINISH it's assumed the input and output buffers are both sized large enough to decompress the entire stream in a single call (this is slightly faster). */ +/* MZ_FINISH implies that there are no more source bytes available beside what's already in the input buffer, and that the output buffer is large enough to hold the rest of the decompressed data. */ +/* Return values: */ +/* MZ_OK on success. Either more input is needed but not available, and/or there's more output to be written but the output buffer is full. */ +/* MZ_STREAM_END if all needed input has been consumed and all output bytes have been written. For zlib streams, the adler-32 of the decompressed data has also been verified. */ +/* MZ_STREAM_ERROR if the stream is bogus. */ +/* MZ_DATA_ERROR if the deflate stream is invalid. */ +/* MZ_PARAM_ERROR if one of the parameters is invalid. */ +/* MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again */ +/* with more input data, or with more room in the output buffer (except when using single call decompression, described above). */ +int mz_inflate(mz_streamp pStream, int flush); + +/* Deinitializes a decompressor. */ +int mz_inflateEnd(mz_streamp pStream); + +/* Single-call decompression. */ +/* Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. */ +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); + +/* Returns a string description of the specified error code, or NULL if the error code is invalid. */ +const char *mz_error(int err); + +/* Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. */ +/* Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. */ +#ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES +typedef unsigned char Byte; +typedef unsigned int uInt; +typedef mz_ulong uLong; +typedef Byte Bytef; +typedef uInt uIntf; +typedef char charf; +typedef int intf; +typedef void *voidpf; +typedef uLong uLongf; +typedef void *voidp; +typedef void *const voidpc; +#define Z_NULL 0 +#define Z_NO_FLUSH MZ_NO_FLUSH +#define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH +#define Z_SYNC_FLUSH MZ_SYNC_FLUSH +#define Z_FULL_FLUSH MZ_FULL_FLUSH +#define Z_FINISH MZ_FINISH +#define Z_BLOCK MZ_BLOCK +#define Z_OK MZ_OK +#define Z_STREAM_END MZ_STREAM_END +#define Z_NEED_DICT MZ_NEED_DICT +#define Z_ERRNO MZ_ERRNO +#define Z_STREAM_ERROR MZ_STREAM_ERROR +#define Z_DATA_ERROR MZ_DATA_ERROR +#define Z_MEM_ERROR MZ_MEM_ERROR +#define Z_BUF_ERROR MZ_BUF_ERROR +#define Z_VERSION_ERROR MZ_VERSION_ERROR +#define Z_PARAM_ERROR MZ_PARAM_ERROR +#define Z_NO_COMPRESSION MZ_NO_COMPRESSION +#define Z_BEST_SPEED MZ_BEST_SPEED +#define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION +#define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION +#define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY +#define Z_FILTERED MZ_FILTERED +#define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY +#define Z_RLE MZ_RLE +#define Z_FIXED MZ_FIXED +#define Z_DEFLATED MZ_DEFLATED +#define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS +#define alloc_func mz_alloc_func +#define free_func mz_free_func +#define internal_state mz_internal_state +#define z_stream mz_stream +#define deflateInit mz_deflateInit +#define deflateInit2 mz_deflateInit2 +#define deflateReset mz_deflateReset +#define deflate mz_deflate +#define deflateEnd mz_deflateEnd +#define deflateBound mz_deflateBound +#define compress mz_compress +#define compress2 mz_compress2 +#define compressBound mz_compressBound +#define inflateInit mz_inflateInit +#define inflateInit2 mz_inflateInit2 +#define inflate mz_inflate +#define inflateEnd mz_inflateEnd +#define uncompress mz_uncompress +#define crc32 mz_crc32 +#define adler32 mz_adler32 +#define MAX_WBITS 15 +#define MAX_MEM_LEVEL 9 +#define zError mz_error +#define ZLIB_VERSION MZ_VERSION +#define ZLIB_VERNUM MZ_VERNUM +#define ZLIB_VER_MAJOR MZ_VER_MAJOR +#define ZLIB_VER_MINOR MZ_VER_MINOR +#define ZLIB_VER_REVISION MZ_VER_REVISION +#define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION +#define zlibVersion mz_version +#define zlib_version mz_version() +#endif /* #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES */ + +#endif /* MINIZ_NO_ZLIB_APIS */ + +#ifdef __cplusplus +} +#endif +#pragma once +#include +#include +#include +#include + +/* ------------------- Types and macros */ +typedef unsigned char mz_uint8; +typedef signed short mz_int16; +typedef unsigned short mz_uint16; +typedef unsigned int mz_uint32; +typedef unsigned int mz_uint; +typedef int64_t mz_int64; +typedef uint64_t mz_uint64; +typedef int mz_bool; + +#define MZ_FALSE (0) +#define MZ_TRUE (1) + +/* Works around MSVC's spammy "warning C4127: conditional expression is constant" message. */ +#ifdef _MSC_VER +#define MZ_MACRO_END while (0, 0) +#else +#define MZ_MACRO_END while (0) +#endif + +#ifdef MINIZ_NO_STDIO +#define MZ_FILE void * +#else +#include +#define MZ_FILE FILE +#endif /* #ifdef MINIZ_NO_STDIO */ + +#ifdef MINIZ_NO_TIME +typedef struct mz_dummy_time_t_tag +{ + int m_dummy; +} mz_dummy_time_t; +#define MZ_TIME_T mz_dummy_time_t +#else +#define MZ_TIME_T time_t +#endif + +#define MZ_ASSERT(x) assert(x) + +#ifdef MINIZ_NO_MALLOC +#define MZ_MALLOC(x) NULL +#define MZ_FREE(x) (void)x, ((void)0) +#define MZ_REALLOC(p, x) NULL +#else +#define MZ_MALLOC(x) malloc(x) +#define MZ_FREE(x) free(x) +#define MZ_REALLOC(p, x) realloc(p, x) +#endif + +#define MZ_MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define MZ_MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN +#define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) +#define MZ_READ_LE32(p) *((const mz_uint32 *)(p)) +#else +#define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U)) +#define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) +#endif + +#define MZ_READ_LE64(p) (((mz_uint64)MZ_READ_LE32(p)) | (((mz_uint64)MZ_READ_LE32((const mz_uint8 *)(p) + sizeof(mz_uint32))) << 32U)) + +#ifdef _MSC_VER +#define MZ_FORCEINLINE __forceinline +#elif defined(__GNUC__) +#define MZ_FORCEINLINE __inline__ __attribute__((__always_inline__)) +#else +#define MZ_FORCEINLINE inline +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +extern void *miniz_def_alloc_func(void *opaque, size_t items, size_t size); +extern void miniz_def_free_func(void *opaque, void *address); +extern void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size); + +#define MZ_UINT16_MAX (0xFFFFU) +#define MZ_UINT32_MAX (0xFFFFFFFFU) + +#ifdef __cplusplus +} +#endif +#pragma once + + +#ifdef __cplusplus +extern "C" { +#endif +/* ------------------- Low-level Compression API Definitions */ + +/* Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly slower, and raw/dynamic blocks will be output more frequently). */ +#define TDEFL_LESS_MEMORY 0 + +/* tdefl_init() compression flags logically OR'd together (low 12 bits contain the max. number of probes per dictionary search): */ +/* TDEFL_DEFAULT_MAX_PROBES: The compressor defaults to 128 dictionary probes per dictionary search. 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ (slowest/best compression). */ +enum +{ + TDEFL_HUFFMAN_ONLY = 0, + TDEFL_DEFAULT_MAX_PROBES = 128, + TDEFL_MAX_PROBES_MASK = 0xFFF +}; + +/* TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before the deflate data, and the Adler-32 of the source data at the end. Otherwise, you'll get raw deflate data. */ +/* TDEFL_COMPUTE_ADLER32: Always compute the adler-32 of the input data (even when not writing zlib headers). */ +/* TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more efficient lazy parsing. */ +/* TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to decrease the compressor's initialization time to the minimum, but the output may vary from run to run given the same input (depending on the contents of memory). */ +/* TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a distance of 1) */ +/* TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled. */ +/* TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. */ +/* TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. */ +/* The low 12 bits are reserved to control the max # of hash probes per dictionary lookup (see TDEFL_MAX_PROBES_MASK). */ +enum +{ + TDEFL_WRITE_ZLIB_HEADER = 0x01000, + TDEFL_COMPUTE_ADLER32 = 0x02000, + TDEFL_GREEDY_PARSING_FLAG = 0x04000, + TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000, + TDEFL_RLE_MATCHES = 0x10000, + TDEFL_FILTER_MATCHES = 0x20000, + TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000, + TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000 +}; + +/* High level compression functions: */ +/* tdefl_compress_mem_to_heap() compresses a block in memory to a heap block allocated via malloc(). */ +/* On entry: */ +/* pSrc_buf, src_buf_len: Pointer and size of source block to compress. */ +/* flags: The max match finder probes (default is 128) logically OR'd against the above flags. Higher probes are slower but improve compression. */ +/* On return: */ +/* Function returns a pointer to the compressed data, or NULL on failure. */ +/* *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. */ +/* The caller must free() the returned block when it's no longer needed. */ +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +/* tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. */ +/* Returns 0 on failure. */ +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +/* Compresses an image to a compressed PNG file in memory. */ +/* On entry: */ +/* pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4. */ +/* The image pitch in bytes per scanline will be w*num_chans. The leftmost pixel on the top scanline is stored first in memory. */ +/* level may range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL */ +/* If flip is true, the image will be flipped on the Y axis (useful for OpenGL apps). */ +/* On return: */ +/* Function returns a pointer to the compressed data, or NULL on failure. */ +/* *pLen_out will be set to the size of the PNG image file. */ +/* The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. */ +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip); +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); + +/* Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. */ +typedef mz_bool (*tdefl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); + +/* tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. */ +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +enum +{ + TDEFL_MAX_HUFF_TABLES = 3, + TDEFL_MAX_HUFF_SYMBOLS_0 = 288, + TDEFL_MAX_HUFF_SYMBOLS_1 = 32, + TDEFL_MAX_HUFF_SYMBOLS_2 = 19, + TDEFL_LZ_DICT_SIZE = 32768, + TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1, + TDEFL_MIN_MATCH_LEN = 3, + TDEFL_MAX_MATCH_LEN = 258 +}; + +/* TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed output block (using static/fixed Huffman codes). */ +#if TDEFL_LESS_MEMORY +enum +{ + TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024, + TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, + TDEFL_MAX_HUFF_SYMBOLS = 288, + TDEFL_LZ_HASH_BITS = 12, + TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, + TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, + TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS +}; +#else +enum +{ + TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024, + TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, + TDEFL_MAX_HUFF_SYMBOLS = 288, + TDEFL_LZ_HASH_BITS = 15, + TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, + TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, + TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS +}; +#endif + +/* The low-level tdefl functions below may be used directly if the above helper functions aren't flexible enough. The low-level functions don't make any heap allocations, unlike the above helper functions. */ +typedef enum { + TDEFL_STATUS_BAD_PARAM = -2, + TDEFL_STATUS_PUT_BUF_FAILED = -1, + TDEFL_STATUS_OKAY = 0, + TDEFL_STATUS_DONE = 1 +} tdefl_status; + +/* Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums */ +typedef enum { + TDEFL_NO_FLUSH = 0, + TDEFL_SYNC_FLUSH = 2, + TDEFL_FULL_FLUSH = 3, + TDEFL_FINISH = 4 +} tdefl_flush; + +/* tdefl's compression state structure. */ +typedef struct +{ + tdefl_put_buf_func_ptr m_pPut_buf_func; + void *m_pPut_buf_user; + mz_uint m_flags, m_max_probes[2]; + int m_greedy_parsing; + mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size; + mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end; + mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer; + mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish; + tdefl_status m_prev_return_status; + const void *m_pIn_buf; + void *m_pOut_buf; + size_t *m_pIn_buf_size, *m_pOut_buf_size; + tdefl_flush m_flush; + const mz_uint8 *m_pSrc; + size_t m_src_buf_left, m_out_buf_ofs; + mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1]; + mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE]; + mz_uint16 m_next[TDEFL_LZ_DICT_SIZE]; + mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE]; + mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE]; +} tdefl_compressor; + +/* Initializes the compressor. */ +/* There is no corresponding deinit() function because the tdefl API's do not dynamically allocate memory. */ +/* pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression. */ +/* If pBut_buf_func is NULL the user should always call the tdefl_compress() API. */ +/* flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) */ +tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +/* Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. */ +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush); + +/* tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. */ +/* tdefl_compress_buffer() always consumes the entire input buffer. */ +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); +mz_uint32 tdefl_get_adler32(tdefl_compressor *d); + +/* Create tdefl_compress() flags given zlib-style compression parameters. */ +/* level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) */ +/* window_bits may be -15 (raw deflate) or 15 (zlib) */ +/* strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED */ +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); + +/* Allocate the tdefl_compressor structure in C so that */ +/* non-C language bindings to tdefl_ API don't need to worry about */ +/* structure size and allocation mechanism. */ +tdefl_compressor *tdefl_compressor_alloc(); +void tdefl_compressor_free(tdefl_compressor *pComp); + +#ifdef __cplusplus +} +#endif +#pragma once + +/* ------------------- Low-level Decompression API Definitions */ + +#ifdef __cplusplus +extern "C" { +#endif +/* Decompression flags used by tinfl_decompress(). */ +/* TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. */ +/* TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. */ +/* TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). */ +/* TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. */ +enum +{ + TINFL_FLAG_PARSE_ZLIB_HEADER = 1, + TINFL_FLAG_HAS_MORE_INPUT = 2, + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, + TINFL_FLAG_COMPUTE_ADLER32 = 8 +}; + +/* High level decompression functions: */ +/* tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc(). */ +/* On entry: */ +/* pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress. */ +/* On return: */ +/* Function returns a pointer to the decompressed data, or NULL on failure. */ +/* *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. */ +/* The caller must call mz_free() on the returned block when it's no longer needed. */ +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +/* tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. */ +/* Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. */ +#define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +/* tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. */ +/* Returns 1 on success or 0 on failure. */ +typedef int (*tinfl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +struct tinfl_decompressor_tag; +typedef struct tinfl_decompressor_tag tinfl_decompressor; + +/* Allocate the tinfl_decompressor structure in C so that */ +/* non-C language bindings to tinfl_ API don't need to worry about */ +/* structure size and allocation mechanism. */ + +tinfl_decompressor *tinfl_decompressor_alloc(); +void tinfl_decompressor_free(tinfl_decompressor *pDecomp); + +/* Max size of LZ dictionary. */ +#define TINFL_LZ_DICT_SIZE 32768 + +/* Return status. */ +typedef enum { + /* This flags indicates the inflator needs 1 or more input bytes to make forward progress, but the caller is indicating that no more are available. The compressed data */ + /* is probably corrupted. If you call the inflator again with more bytes it'll try to continue processing the input but this is a BAD sign (either the data is corrupted or you called it incorrectly). */ + /* If you call it again with no input you'll just get TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS again. */ + TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS = -4, + + /* This flag indicates that one or more of the input parameters was obviously bogus. (You can try calling it again, but if you get this error the calling code is wrong.) */ + TINFL_STATUS_BAD_PARAM = -3, + + /* This flags indicate the inflator is finished but the adler32 check of the uncompressed data didn't match. If you call it again it'll return TINFL_STATUS_DONE. */ + TINFL_STATUS_ADLER32_MISMATCH = -2, + + /* This flags indicate the inflator has somehow failed (bad code, corrupted input, etc.). If you call it again without resetting via tinfl_init() it it'll just keep on returning the same status failure code. */ + TINFL_STATUS_FAILED = -1, + + /* Any status code less than TINFL_STATUS_DONE must indicate a failure. */ + + /* This flag indicates the inflator has returned every byte of uncompressed data that it can, has consumed every byte that it needed, has successfully reached the end of the deflate stream, and */ + /* if zlib headers and adler32 checking enabled that it has successfully checked the uncompressed data's adler32. If you call it again you'll just get TINFL_STATUS_DONE over and over again. */ + TINFL_STATUS_DONE = 0, + + /* This flag indicates the inflator MUST have more input data (even 1 byte) before it can make any more forward progress, or you need to clear the TINFL_FLAG_HAS_MORE_INPUT */ + /* flag on the next call if you don't have any more source data. If the source data was somehow corrupted it's also possible (but unlikely) for the inflator to keep on demanding input to */ + /* proceed, so be sure to properly set the TINFL_FLAG_HAS_MORE_INPUT flag. */ + TINFL_STATUS_NEEDS_MORE_INPUT = 1, + + /* This flag indicates the inflator definitely has 1 or more bytes of uncompressed data available, but it cannot write this data into the output buffer. */ + /* Note if the source compressed data was corrupted it's possible for the inflator to return a lot of uncompressed data to the caller. I've been assuming you know how much uncompressed data to expect */ + /* (either exact or worst case) and will stop calling the inflator and fail after receiving too much. In pure streaming scenarios where you have no idea how many bytes to expect this may not be possible */ + /* so I may need to add some code to address this. */ + TINFL_STATUS_HAS_MORE_OUTPUT = 2 +} tinfl_status; + +/* Initializes the decompressor to its initial state. */ +#define tinfl_init(r) \ + do \ + { \ + (r)->m_state = 0; \ + } \ + MZ_MACRO_END +#define tinfl_get_adler32(r) (r)->m_check_adler32 + +/* Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. */ +/* This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. */ +tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); + +/* Internal/private bits follow. */ +enum +{ + TINFL_MAX_HUFF_TABLES = 3, + TINFL_MAX_HUFF_SYMBOLS_0 = 288, + TINFL_MAX_HUFF_SYMBOLS_1 = 32, + TINFL_MAX_HUFF_SYMBOLS_2 = 19, + TINFL_FAST_LOOKUP_BITS = 10, + TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS +}; + +typedef struct +{ + mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; + mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; +} tinfl_huff_table; + +#if MINIZ_HAS_64BIT_REGISTERS +#define TINFL_USE_64BIT_BITBUF 1 +#else +#define TINFL_USE_64BIT_BITBUF 0 +#endif + +#if TINFL_USE_64BIT_BITBUF +typedef mz_uint64 tinfl_bit_buf_t; +#define TINFL_BITBUF_SIZE (64) +#else +typedef mz_uint32 tinfl_bit_buf_t; +#define TINFL_BITBUF_SIZE (32) +#endif + +struct tinfl_decompressor_tag +{ + mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; + tinfl_bit_buf_t m_bit_buf; + size_t m_dist_from_out_buf_start; + tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; + mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; +}; + +#ifdef __cplusplus +} +#endif + +#pragma once + + +/* ------------------- ZIP archive reading/writing */ + +#ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef __cplusplus +extern "C" { +#endif + +enum +{ + /* Note: These enums can be reduced as needed to save memory or stack space - they are pretty conservative. */ + MZ_ZIP_MAX_IO_BUF_SIZE = 64 * 1024, + MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 512, + MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 512 +}; + +typedef struct +{ + /* Central directory file index. */ + mz_uint32 m_file_index; + + /* Byte offset of this entry in the archive's central directory. Note we currently only support up to UINT_MAX or less bytes in the central dir. */ + mz_uint64 m_central_dir_ofs; + + /* These fields are copied directly from the zip's central dir. */ + mz_uint16 m_version_made_by; + mz_uint16 m_version_needed; + mz_uint16 m_bit_flag; + mz_uint16 m_method; + +#ifndef MINIZ_NO_TIME + MZ_TIME_T m_time; +#endif + + /* CRC-32 of uncompressed data. */ + mz_uint32 m_crc32; + + /* File's compressed size. */ + mz_uint64 m_comp_size; + + /* File's uncompressed size. Note, I've seen some old archives where directory entries had 512 bytes for their uncompressed sizes, but when you try to unpack them you actually get 0 bytes. */ + mz_uint64 m_uncomp_size; + + /* Zip internal and external file attributes. */ + mz_uint16 m_internal_attr; + mz_uint32 m_external_attr; + + /* Entry's local header file offset in bytes. */ + mz_uint64 m_local_header_ofs; + + /* Size of comment in bytes. */ + mz_uint32 m_comment_size; + + /* MZ_TRUE if the entry appears to be a directory. */ + mz_bool m_is_directory; + + /* MZ_TRUE if the entry uses encryption/strong encryption (which miniz_zip doesn't support) */ + mz_bool m_is_encrypted; + + /* MZ_TRUE if the file is not encrypted, a patch file, and if it uses a compression method we support. */ + mz_bool m_is_supported; + + /* Filename. If string ends in '/' it's a subdirectory entry. */ + /* Guaranteed to be zero terminated, may be truncated to fit. */ + char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; + + /* Comment field. */ + /* Guaranteed to be zero terminated, may be truncated to fit. */ + char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; + +} mz_zip_archive_file_stat; + +typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n); +typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n); +typedef mz_bool (*mz_file_needs_keepalive)(void *pOpaque); + +struct mz_zip_internal_state_tag; +typedef struct mz_zip_internal_state_tag mz_zip_internal_state; + +typedef enum { + MZ_ZIP_MODE_INVALID = 0, + MZ_ZIP_MODE_READING = 1, + MZ_ZIP_MODE_WRITING = 2, + MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 +} mz_zip_mode; + +typedef enum { + MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, + MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, + MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800, + MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG = 0x1000, /* if enabled, mz_zip_reader_locate_file() will be called on each file as its validated to ensure the func finds the file in the central dir (intended for testing) */ + MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY = 0x2000, /* validate the local headers, but don't decompress the entire file and check the crc32 */ + MZ_ZIP_FLAG_WRITE_ZIP64 = 0x4000, /* always use the zip64 file format, instead of the original zip file format with automatic switch to zip64. Use as flags parameter with mz_zip_writer_init*_v2 */ + MZ_ZIP_FLAG_WRITE_ALLOW_READING = 0x8000, + MZ_ZIP_FLAG_ASCII_FILENAME = 0x10000 +} mz_zip_flags; + +typedef enum { + MZ_ZIP_TYPE_INVALID = 0, + MZ_ZIP_TYPE_USER, + MZ_ZIP_TYPE_MEMORY, + MZ_ZIP_TYPE_HEAP, + MZ_ZIP_TYPE_FILE, + MZ_ZIP_TYPE_CFILE, + MZ_ZIP_TOTAL_TYPES +} mz_zip_type; + +/* miniz error codes. Be sure to update mz_zip_get_error_string() if you add or modify this enum. */ +typedef enum { + MZ_ZIP_NO_ERROR = 0, + MZ_ZIP_UNDEFINED_ERROR, + MZ_ZIP_TOO_MANY_FILES, + MZ_ZIP_FILE_TOO_LARGE, + MZ_ZIP_UNSUPPORTED_METHOD, + MZ_ZIP_UNSUPPORTED_ENCRYPTION, + MZ_ZIP_UNSUPPORTED_FEATURE, + MZ_ZIP_FAILED_FINDING_CENTRAL_DIR, + MZ_ZIP_NOT_AN_ARCHIVE, + MZ_ZIP_INVALID_HEADER_OR_CORRUPTED, + MZ_ZIP_UNSUPPORTED_MULTIDISK, + MZ_ZIP_DECOMPRESSION_FAILED, + MZ_ZIP_COMPRESSION_FAILED, + MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE, + MZ_ZIP_CRC_CHECK_FAILED, + MZ_ZIP_UNSUPPORTED_CDIR_SIZE, + MZ_ZIP_ALLOC_FAILED, + MZ_ZIP_FILE_OPEN_FAILED, + MZ_ZIP_FILE_CREATE_FAILED, + MZ_ZIP_FILE_WRITE_FAILED, + MZ_ZIP_FILE_READ_FAILED, + MZ_ZIP_FILE_CLOSE_FAILED, + MZ_ZIP_FILE_SEEK_FAILED, + MZ_ZIP_FILE_STAT_FAILED, + MZ_ZIP_INVALID_PARAMETER, + MZ_ZIP_INVALID_FILENAME, + MZ_ZIP_BUF_TOO_SMALL, + MZ_ZIP_INTERNAL_ERROR, + MZ_ZIP_FILE_NOT_FOUND, + MZ_ZIP_ARCHIVE_TOO_LARGE, + MZ_ZIP_VALIDATION_FAILED, + MZ_ZIP_WRITE_CALLBACK_FAILED, + MZ_ZIP_TOTAL_ERRORS +} mz_zip_error; + +typedef struct +{ + mz_uint64 m_archive_size; + mz_uint64 m_central_directory_file_ofs; + + /* We only support up to UINT32_MAX files in zip64 mode. */ + mz_uint32 m_total_files; + mz_zip_mode m_zip_mode; + mz_zip_type m_zip_type; + mz_zip_error m_last_error; + + mz_uint64 m_file_offset_alignment; + + mz_alloc_func m_pAlloc; + mz_free_func m_pFree; + mz_realloc_func m_pRealloc; + void *m_pAlloc_opaque; + + mz_file_read_func m_pRead; + mz_file_write_func m_pWrite; + mz_file_needs_keepalive m_pNeeds_keepalive; + void *m_pIO_opaque; + + mz_zip_internal_state *m_pState; + +} mz_zip_archive; + +typedef struct +{ + mz_zip_archive *pZip; + mz_uint flags; + + int status; +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + mz_uint file_crc32; +#endif + mz_uint64 read_buf_size, read_buf_ofs, read_buf_avail, comp_remaining, out_buf_ofs, cur_file_ofs; + mz_zip_archive_file_stat file_stat; + void *pRead_buf; + void *pWrite_buf; + + size_t out_blk_remain; + + tinfl_decompressor inflator; + +} mz_zip_reader_extract_iter_state; + +/* -------- ZIP reading */ + +/* Inits a ZIP archive reader. */ +/* These functions read and validate the archive's central directory. */ +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags); + +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags); + +#ifndef MINIZ_NO_STDIO +/* Read a archive from a disk file. */ +/* file_start_ofs is the file offset where the archive actually begins, or 0. */ +/* actual_archive_size is the true total size of the archive, which may be smaller than the file's actual size on disk. If zero the entire file is treated as the archive. */ +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags); +mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size); + +/* Read an archive from an already opened FILE, beginning at the current file position. */ +/* The archive is assumed to be archive_size bytes long. If archive_size is < 0, then the entire rest of the file is assumed to contain the archive. */ +/* The FILE will NOT be closed when mz_zip_reader_end() is called. */ +mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags); +#endif + +/* Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. */ +mz_bool mz_zip_reader_end(mz_zip_archive *pZip); + +/* -------- ZIP reading or writing */ + +/* Clears a mz_zip_archive struct to all zeros. */ +/* Important: This must be done before passing the struct to any mz_zip functions. */ +void mz_zip_zero_struct(mz_zip_archive *pZip); + +mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip); +mz_zip_type mz_zip_get_type(mz_zip_archive *pZip); + +/* Returns the total number of files in the archive. */ +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); + +mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip); +mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip); +MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip); + +/* Reads n bytes of raw archive data, starting at file offset file_ofs, to pBuf. */ +size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n); + +/* All mz_zip funcs set the m_last_error field in the mz_zip_archive struct. These functions retrieve/manipulate this field. */ +/* Note that the m_last_error functionality is not thread safe. */ +mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num); +mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip); +mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip); +mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip); +const char *mz_zip_get_error_string(mz_zip_error mz_err); + +/* MZ_TRUE if the archive file entry is a directory entry. */ +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index); + +/* MZ_TRUE if the file is encrypted/strong encrypted. */ +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index); + +/* MZ_TRUE if the compression method is supported, and the file is not encrypted, and the file is not a compressed patch file. */ +mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index); + +/* Retrieves the filename of an archive file entry. */ +/* Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. */ +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size); + +/* Attempts to locates a file in the archive's central directory. */ +/* Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH */ +/* Returns -1 if the file cannot be found. */ +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); +int mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *file_index); + +/* Returns detailed information about an archive file entry. */ +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat); + +/* MZ_TRUE if the file is in zip64 format. */ +/* A file is considered zip64 if it contained a zip64 end of central directory marker, or if it contained any zip64 extended file information fields in the central directory. */ +mz_bool mz_zip_is_zip64(mz_zip_archive *pZip); + +/* Returns the total central directory size in bytes. */ +/* The current max supported size is <= MZ_UINT32_MAX. */ +size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip); + +/* Extracts a archive file to a memory buffer using no memory allocation. */ +/* There must be at least enough room on the stack to store the inflator's state (~34KB or so). */ +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); + +/* Extracts a archive file to a memory buffer. */ +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags); + +/* Extracts a archive file to a dynamically allocated heap buffer. */ +/* The memory will be allocated via the mz_zip_archive's alloc/realloc functions. */ +/* Returns NULL and sets the last error on failure. */ +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags); +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags); + +/* Extracts a archive file using a callback function to output the file's data. */ +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); + +/* Extract a file iteratively */ +mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); +mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); +size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size); +mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState); + +#ifndef MINIZ_NO_STDIO +/* Extracts a archive file to a disk file and sets its last accessed and modified times. */ +/* This function only extracts files, not archive directory records. */ +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags); + +/* Extracts a archive file starting at the current position in the destination FILE stream. */ +mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *File, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags); +#endif + +#if 0 +/* TODO */ + typedef void *mz_zip_streaming_extract_state_ptr; + mz_zip_streaming_extract_state_ptr mz_zip_streaming_extract_begin(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); + uint64_t mz_zip_streaming_extract_get_size(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); + uint64_t mz_zip_streaming_extract_get_cur_ofs(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); + mz_bool mz_zip_streaming_extract_seek(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, uint64_t new_ofs); + size_t mz_zip_streaming_extract_read(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, void *pBuf, size_t buf_size); + mz_bool mz_zip_streaming_extract_end(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); +#endif + +/* This function compares the archive's local headers, the optional local zip64 extended information block, and the optional descriptor following the compressed data vs. the data in the central directory. */ +/* It also validates that each file can be successfully uncompressed unless the MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY is specified. */ +mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); + +/* Validates an entire archive by calling mz_zip_validate_file() on each file. */ +mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags); + +/* Misc utils/helpers, valid for ZIP reading or writing */ +mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr); +mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr); + +/* Universal end function - calls either mz_zip_reader_end() or mz_zip_writer_end(). */ +mz_bool mz_zip_end(mz_zip_archive *pZip); + +/* -------- ZIP writing */ + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +/* Inits a ZIP archive writer. */ +/*Set pZip->m_pWrite (and pZip->m_pIO_opaque) before calling mz_zip_writer_init or mz_zip_writer_init_v2*/ +/*The output is streamable, i.e. file_ofs in mz_file_write_func always increases only by n*/ +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); +mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags); + +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size); +mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags); + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning); +mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags); +mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags); +#endif + +/* Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. */ +/* For archives opened using mz_zip_reader_init_file, pFilename must be the archive's filename so it can be reopened for writing. If the file can't be reopened, mz_zip_reader_end() will be called. */ +/* For archives opened using mz_zip_reader_init_mem, the memory block must be growable using the realloc callback (which defaults to realloc unless you've overridden it). */ +/* Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. */ +/* Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before */ +/* the archive is finalized the file's central directory will be hosed. */ +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename); +mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); + +/* Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. */ +/* To add a directory entry, call this method with an archive name ending in a forwardslash with an empty buffer. */ +/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags); + +/* Like mz_zip_writer_add_mem(), except you can specify a file comment field, and optionally supply the function with already compressed data. */ +/* uncomp_size/uncomp_crc32 are only used if the MZ_ZIP_FLAG_COMPRESSED_DATA flag is specified. */ +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + mz_uint64 uncomp_size, mz_uint32 uncomp_crc32); + +mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, const char *user_extra_data_local, mz_uint user_extra_data_local_len, + const char *user_extra_data_central, mz_uint user_extra_data_central_len); + +#ifndef MINIZ_NO_STDIO +/* Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. */ +/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + +/* Like mz_zip_writer_add_file(), except the file data is read from the specified FILE stream. */ +mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 size_to_add, + const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len, + const char *user_extra_data_central, mz_uint user_extra_data_central_len); +#endif + +/* Adds a file to an archive by fully cloning the data from another archive. */ +/* This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data (it may add or modify the zip64 local header extra data field), and the optional descriptor following the compressed data. */ +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index); + +/* Finalizes the archive by writing the central directory records followed by the end of central directory record. */ +/* After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). */ +/* An archive must be manually finalized by calling this function for it to be valid. */ +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); + +/* Finalizes a heap archive, returning a poiner to the heap block and its size. */ +/* The heap block will be allocated using the mz_zip_archive's alloc/realloc callbacks. */ +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize); + +/* Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. */ +/* Note for the archive to be valid, it *must* have been finalized before ending (this function will not do it for you). */ +mz_bool mz_zip_writer_end(mz_zip_archive *pZip); + +/* -------- Misc. high-level helper functions: */ + +/* mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive. */ +/* Note this is NOT a fully safe operation. If it crashes or dies in some way your archive can be left in a screwed up state (without a central directory). */ +/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ +/* TODO: Perhaps add an option to leave the existing central dir in place in case the add dies? We could then truncate the file (so the old central dir would be at the end) if something goes wrong. */ +mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); +mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr); + +/* Reads a single file from an archive into a heap block. */ +/* If pComment is not NULL, only the file with the specified comment will be extracted. */ +/* Returns NULL on failure. */ +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags); +void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr); + +#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */ + +#ifdef __cplusplus +} +#endif + +#endif /* MINIZ_NO_ARCHIVE_APIS */ diff --git a/include/miniz/readme.md b/include/miniz/readme.md new file mode 100755 index 0000000..74b7eb3 --- /dev/null +++ b/include/miniz/readme.md @@ -0,0 +1,37 @@ +## Miniz + +Miniz is a lossless, high performance data compression library in a single source file that implements the zlib (RFC 1950) and Deflate (RFC 1951) compressed data format specification standards. It supports the most commonly used functions exported by the zlib library, but is a completely independent implementation so zlib's licensing requirements do not apply. Miniz also contains simple to use functions for writing .PNG format image files and reading/writing/appending .ZIP format archives. Miniz's compression speed has been tuned to be comparable to zlib's, and it also has a specialized real-time compressor function designed to compare well against fastlz/minilzo. + +## Usage + +Please use the files from the [releases page](https://github.com/richgel999/miniz/releases) in your projects. Do not use the git checkout directly! The different source and header files are [amalgamated](https://www.sqlite.org/amalgamation.html) into one `miniz.c`/`miniz.h` pair in a build step (`amalgamate.sh`). Include `miniz.c` and `miniz.h` in your project to use Miniz. + +## Features + +* MIT licensed +* A portable, single source and header file library written in plain C. Tested with GCC, clang and Visual Studio. +* Easily tuned and trimmed down by defines +* A drop-in replacement for zlib's most used API's (tested in several open source projects that use zlib, such as libpng and libzip). +* Fills a single threaded performance vs. compression ratio gap between several popular real-time compressors and zlib. For example, at level 1, miniz.c compresses around 5-9% better than minilzo, but is approx. 35% slower. At levels 2-9, miniz.c is designed to compare favorably against zlib's ratio and speed. See the miniz performance comparison page for example timings. +* Not a block based compressor: miniz.c fully supports stream based processing using a coroutine-style implementation. The zlib-style API functions can be called a single byte at a time if that's all you've got. +* Easy to use. The low-level compressor (tdefl) and decompressor (tinfl) have simple state structs which can be saved/restored as needed with simple memcpy's. The low-level codec API's don't use the heap in any way. +* Entire inflater (including optional zlib header parsing and Adler-32 checking) is implemented in a single function as a coroutine, which is separately available in a small (~550 line) source file: miniz_tinfl.c +* A fairly complete (but totally optional) set of .ZIP archive manipulation and extraction API's. The archive functionality is intended to solve common problems encountered in embedded, mobile, or game development situations. (The archive API's are purposely just powerful enough to write an entire archiver given a bit of additional higher-level logic.) + +## Known Problems + +* No support for encrypted archives. Not sure how useful this stuff is in practice. +* Minimal documentation. The assumption is that the user is already familiar with the basic zlib API. I need to write an API wiki - for now I've tried to place key comments before each enum/API, and I've included 6 examples that demonstrate how to use the module's major features. + +## Special Thanks + +Thanks to Alex Evans for the PNG writer function. Also, thanks to Paul Holden and Thorsten Scheuermann for feedback and testing, Matt Pritchard for all his encouragement, and Sean Barrett's various public domain libraries for inspiration (and encouraging me to write miniz.c in C, which was much more enjoyable and less painful than I thought it would be considering I've been programming in C++ for so long). + +Thanks to Bruce Dawson for reporting a problem with the level_and_flags archive API parameter (which is fixed in v1.12) and general feedback, and Janez Zemva for indirectly encouraging me into writing more examples. + +## Patents + +I was recently asked if miniz avoids patent issues. miniz purposely uses the same core algorithms as the ones used by zlib. The compressor uses vanilla hash chaining as described [here](http://www.gzip.org/zlib/rfc-deflate.html#algorithm). Also see the [gzip FAQ](http://www.gzip.org/#faq11). In my opinion, if miniz falls prey to a patent attack then zlib/gzip are likely to be at serious risk too. + + +[![Build Status](https://travis-ci.org/uroni/miniz.svg?branch=master)](https://travis-ci.org/uroni/miniz) \ No newline at end of file diff --git a/include/modality/ebsd/detector.hpp b/include/modality/ebsd/detector.hpp new file mode 100644 index 0000000..753f8d5 --- /dev/null +++ b/include/modality/ebsd/detector.hpp @@ -0,0 +1,635 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _DETECTOR_H_ +#define _DETECTOR_H_ + +#include +#include +#include + +#include "H5Cpp.h" + +#include "util/fft.hpp" +#include "util/image.hpp" +#include "idx/base.hpp" + +namespace emsphinx { + + namespace ebsd { + //@brief: helper for describing the geometry of a detector + template + struct Geometry { + //parameters to define the geometry of an EBSD detector + Real dTlt, dOmg;//detector tilt/rotation in degrees + Real sTlt, sOmg;//sample tilt/rotation in degrees degrees + size_t w , h ;//camera width/height in pixels + Real pX , pY ;//pixel width/height in microns + Real cX , cY ;//x/y pattern center position relative to center of image (in pixels) + Real sDst ;//scintillator distance in microns + bool circ ;//true / false if a circular mask should/shouldn't be used + bool flip ;//true / false if the images are vertically flipped (image coordinate system / cartesian coordinat system) [origin top left/origin bottom left] + + //@brief: construct an empty geometry + Geometry() : dTlt(0), dOmg(0), sTlt(0), sOmg(0), w(0), h(0), pX(0), pY(0), cX(0), cY(0), sDst(0), circ(false), flip(false) {} + + //@brief : set the sample tilt + //@param sig: sample tilt in degrees (e.g. 70) + void sampleTilt(const Real sig) {sTlt = sig;} + + //@brief : set the camera tilt + //@param sig: camera tilt in degrees (e.g. 15) + void cameraTilt(const Real thetac) {dTlt = thetac;} + + //@brief : set camera dimensions using EMsoft parameters + //@param numsx: width of detector in pixels + //@param numsy: height of detector in pixels + //@param delta: pixel size in microns + void cameraSize(const size_t numsx, const size_t numsy, const Real delta) {w = numsx; h = numsy; pX = pY = delta;} + + //@brief : set pattern center using EMsoft parameters + //@param xpc: x pattern center in fractional pixels relative to center of image + //@param ypc: y pattern center in fractional pixels relative to center of image + //@param L : scintillator distance in microns + void patternCenter(const Real xpc, const Real ypc, const Real L) {cX = xpc; cY = ypc; sDst = L;} + + //@brief : set pattern center using tsl parameters + //@param xStar: tsl x^* + //@param yStar: tsl y^* + //@param zStar: tsl z^* + //@param note : camera and pixel size need to be set first + void patternCenterTSL(const Real xStar, const Real yStar, const Real zStar); + + //@brief : set pattern center using oxford parameters + //@param xStar: oxford x^* + //@param yStar: oxford y^* + //@param zStar: oxford z^* + //@param note : camera and pixel size need to be set first + void patternCenterOxford(const Real xStar, const Real yStar, const Real zStar); + + //@brief : set pattern center using bruker parameters + //@param xStar: bruker x^* + //@param yStar: bruker y^* + //@param zStar: bruker z^* + //@param note : camera and pixel size need to be set first + void patternCenterBruker(const Real xStar, const Real yStar, const Real zStar); + + //@brief : build a detector using electron channeling pattern parameters + //@param dim : size (sidelength) of ECP in pixels + //@param theta: maximum procession angle in degrees + void ecp(const size_t dim, const Real theta); + + //@brief : set if a circular mask should be used + //@param mask: true/false if a circular mask should/shouldn't be used + void maskPattern(const bool mask) {circ = mask;} + + //@brief : set if detector images are vertically flipped + //@param flipVert: true/false detector images are/aren't vertically flipped + void flipPattern(const bool flipVert) {flip = flipVert;} + + //@brief : rebin a detector (group pixels into nxn super pixel) + //@param n: side length of block of pixels to merge + //@return : binned geometry + Geometry bin(const size_t n) const; + + //@brief : bilinearlly interpolate a pixel value for the given direction + //@param n : unit direction to interpolate value for in the sample reference frame (pattern center is in +z direction) + //@param pix: [optional] location to store interpolation pixel + //@param flp: [optional] is the image to interpolate from flipped vertically + //@return : true / false if the direction lies inside / outside the detector + bool interpolatePixel(Real const * const n, image::BiPix * const pix = NULL, const bool flp = false) const; + + //@brief : get the sample direction the points toward the specified pixel + //@param X: fractional x position on detector (relative to detector center) + //@param Y: fractional y position on detector (relative to detector center) + //@param n: location to write unit direction pointing to (X,Y) in sample reference frame + void sampleDir(const Real X, const Real Y, Real * const n) const; + + //@brief : approximate the solid angle captured by a detector + //@param gridRes: side length of square lambert projection to grid over + //@return : fractional solid angle i.e [0,1] not [0,4*pi] + Real solidAngle(size_t gridRes) const; + + //@brief : create a new detector geometry by rescaling the pixels size while maintaining solid angle + //@param scale: rescaling factor, must lie in [0, min(w, h)] with values < 1 corresponding to increasing pixel density + //@note : this is analogous to continuous camera binning with e.g. scale = 4.0 corresponding to 4x4 camera binning + Geometry rescale(const Real scale) const; + + //@brief : create a new detector geometry by rescaling the pixels size while maintaining solid angle + //@param wNew: scaled detector width + //@param hNew: scaled detector height + Geometry rescale(size_t wNew, const size_t hNew) const; + + //@brief: compute quaternion to rotate detector such that is centered around the north pole + //@return: quaternion as wxyz + std::array northPoleQuat() const; + + //@brief : compute the rescale factor required to make average detector pixel size the same as an average pixel on a square spherical grid + //@param dim: side length of square spherical grid to match pixel size to + //@return : geometry scale factor to make average pixel sizes the same + Real scaleFactor(const size_t dim) const; + + //@brief : read (partial) detector geometry info from an EMsoft master pattern file + //@param grp: folder in h5 file to read from (e.g. "/NMLparameters/EBSDNameList/") + //@note : this doesn't get the sample tilt which is a monte carlo parameter + void readEMsoft(H5::Group grp); + }; + + //helper for back projecting from an EBSD detector -> sphere + template + struct BackProjector : public emsphinx::BackProjector { + struct Constants;//helper struct to hold read only constants + const std::shared_ptr pLut;//read only values (can be shared across threads) + std::vector rPat;//space for pattern as real + fft::vector sWrk;//work space for pattern rescaler + std::vector iVal;//space for intermediate interpolation result + + //@brief : construct a back projector + //@param geo: detector geometry to back project from + //@param dim: side length of square legendre grid to back project onto + //@param fct: additional scale factor for detector resizing + //@param qu : quaternion to rotate detector location by + BackProjector(Geometry geo, const size_t dim, const Real fct, Real const * const qu = NULL); + + //@brief : unproject a pattern from the detector to a square grid + //@param pat: pattern to back project to unit sphere + //@param sph: location to write back projected pattern + //@param iq : location to write pattern image quality (NULL to skip computation) + //@return : sqrt(integral of pat^2) (properly weighted on the sphere), 0 if sum == false + Real unproject(Real * const pat, Real * const sph, Real * const iq = NULL); + + //@brief : get a copy of the stored back projector + //@return: unique pointer to copy of current projector + std::unique_ptr > clone() const {return std::unique_ptr(new BackProjector(*this));} + + //@brief : build a mask of spherical pixels covered by the detector + //@param sph: location to write mask + void mask(Real * const sph); + }; + + //helper struct to hold read only constants needed by BackProjector (for sharing across threads) + template + struct BackProjector::Constants { + std::vector< image::BiPix > iPts;//interpolation coefficients + std::vector omeg;//solid angles of iPts (relative to average spherical pixel size) + Real omgW;//sum of omeg (in window) + Real omgS;//sum of omeg (over sphere) + image::Rescaler sclr;//pattern rescaler + const bool flip;//do input patterns need to be vertically flipped + + //@brief : construct a back projector + //@param geo: detector geometry to back project from + //@param dim: side length of square legendre grid to back project onto + //@param fct: additional scale factor for detector resizing + //@param qu : quaternion to rotate detector location by + Constants(Geometry geo, const size_t dim, const Real fct, Real const * const qu = NULL); + }; + }//ebsd + +}//emsphinx + +//////////////////////////////////////////////////////////////////////// +// Implementations // +//////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include "constants.hpp" +#include "sht/square_sht.hpp" +#include "sht/sht_xcorr.hpp" +#include "xtal/rotations.hpp" +#include "xtal/quaternion.hpp" + +namespace emsphinx { + + namespace ebsd { + + //////////////////////////////////////////////////////////////////////// + // Geometry // + //////////////////////////////////////////////////////////////////////// + + //@brief : set pattern center using tsl parameters + //@param xStar: tsl x^* + //@param yStar: tsl y^* + //@param zStar: tsl z^* + //@param note : camera and pixel size need to be set first + template + void Geometry::patternCenterTSL(const Real xStar, const Real yStar, const Real zStar) { + cX = xStar * w - Real(w) / 2; + cY = yStar * w - Real(h) / 2; + sDst = zStar * w * pX; + } + + //@brief : set pattern center using oxford parameters + //@param xStar: oxford x^* + //@param yStar: oxford y^* + //@param zStar: oxford z^* + //@param note : camera and pixel size need to be set first + template + void Geometry::patternCenterOxford(const Real xStar, const Real yStar, const Real zStar) { + cX = (xStar - Real(0.5)) * w; + cY = (yStar - Real(0.5)) * h; + sDst = zStar * w * pX; + + } + + //@brief : set pattern center using bruker parameters + //@param xStar: bruker x^* + //@param yStar: bruker y^* + //@param zStar: bruker z^* + //@param note : camera and pixel size need to be set first + template + void Geometry::patternCenterBruker(const Real xStar, const Real yStar, const Real zStar) { + cX = (xStar - Real(0.5)) * w; + cY = (Real(0.5) - yStar) * h; + sDst = zStar * h * pX; + } + + //@brief : build a detector using electron channeling pattern parameters + //@param dim : size (sidelength) of ECP in pixels + //@param theta: maximum procession angle in degrees + template + void Geometry::ecp(const size_t dim, const Real theta) { + sTlt = sOmg = 0;//no sample tilts + dTlt = -90;//detector directly above sample + dOmg = 0;//no detector rotation + cX = cY = 0;//no pattern center adjustment + circ = true ;//maximum procession angle -> circular patterns + flip = false;//we shouldn't need to flip the pattern + w = h = dim;//save effective detector size + sDst = 10000;//select an arbitrary detector distance (I've used a common working distance) + + //compute effective pixel size + const Real dTheta = theta * emsphinx::Constants::pi / (dim * 90);//what is the size of a single pixel in radians + pX = pY = std::tan(dTheta) * sDst;//compute the size of a pixel compared to the arbitrary detector distance + } + + //@brief : rebin a detector (group pixels into nxn super pixel) + //@param n: side length of block of pixels to merge + //@return : binned geometry + template + Geometry Geometry::bin(const size_t n) const { + if(n == 0) throw std::runtime_error("binning number must be at least 1"); + if(n == 1) return *this; + if(w % n != 0 || h % n != 0) throw std::runtime_error("binning factor must be a divisor of both height and width"); + Geometry geo(*this);//copy everything + Real bW = Real(w) / n;//compute fractional super pixel width + Real bH = Real(h) / n;//compute fractional super pixel height + geo.w = (size_t) std::ceil(bW);//adjust detector size + geo.h = (size_t) std::ceil(bH);//adjust detector size + geo.pX *= n;//adjust pixel width + geo.pY *= n;//adjust pixel height + geo.cX /= n;//adjust pixel width + geo.cY /= n;//adjust pixel height + /* + //handling non exact divisor binning by converting pattern center to be relative to origin before scaling + //this assumes that the first superpixel is at (0,0) and extra subpixels are cropped off + const Real aX = cX + Real(0.5) * w;//pattern center x in fractional pixels relative to origin + const Real aY = cY + Real(0.5) * h;//pattern center y in fractional pixels relative to origin + geo.cX = (aX / n) - Real(0.5) * geo.w;//scale pattern center x relative to origin and the update back to relative to center + geo.cY = (aY / n) - Real(0.5) * geo.h;//scale pattern center y relative to origin and the update back to relative to center + */ + return geo; + } + + //@brief : bilinearlly interpolate a pixel value for the given direction + //@param n : unit direction to interpolate value for in the sample reference frame (pattern center is in +z direction) + //@param pix: [optional] location to store interpolation pixel + //@param flp: [optional] is the image to interpolate from flipped vertically + //@return : true / false if the direction lies inside / outside the detector + template + bool Geometry::interpolatePixel(Real const * const n, image::BiPix * const pix, const bool flp) const { + //sanity check + if(std::signbit(n[2])) return false;//can't project through sample + if(0 != dOmg || 0 != sOmg) throw std::runtime_error("omega tilt not yet supported"); + + //compute angle between detector and sample normal + static const Real degToRad = emsphinx::Constants::pi / 180; + const Real alpha = (Real(90) - sTlt + dTlt) * degToRad;//angle between sample and detector is 90 with no tilts, increasing sample tilt decreases the angle, increasing camera tilt increases the angle + if(std::fabs(alpha) > emsphinx::Constants::pi_2) throw std::runtime_error("pattern center not on detector");//beyond +/-90 the detector is behind the sample + + //compute sin / cos of angle and distance to detector + const Real sA = std::sin(alpha); + const Real cA = std::sqrt(Real(1) - sA * sA);//cos(alpha) + const Real d = sDst / (n[0] * sA + n[2] * cA); + if(std::signbit(d)) return false;//negative distance + + //compute offset from pattern center in microns + const Real x = n[1] * d; + const Real y = (sA * n[2] - cA * n[0]) * d; + + //convert from microns to fractional position (0->100%) and check if we're inside the detector + const Real X = (cX + x / pX) / w + Real(0.5); + Real Y = (cY + y / pY) / h + Real(0.5); + + // check if we're inside the detector + if(std::signbit(X) || std::signbit(Y) || X > 1 || Y > 1) return false;//outside of frame + if(flp) Y = Real(1) - Y; + + //check against circular mask if needed + if(circ) { + const Real dX = (X - Real(0.5)) * w;//horizontal distance from center in pixels + const Real dY = (Y - Real(0.5)) * h;//vertical distance from center in pixels + const Real r = Real(std::min(w, h)) / 2;//radius of circular mask + if(r * r < dX * dX + dY * dY) return false; + } + + //if we made it this far we're in frame, compute relative contribution from neighboring pixels + if(pix != NULL) pix->bilinearCoeff(X, Y, w, h); + return true; + } + + //@brief : get the sample direction the points toward the specified pixel + //@param X: fractional x position on detector (relative to detector center) + //@param Y: fractional y position on detector (relative to detector center) + //@param n: location to write unit direction pointing to (X,Y) in sample reference frame + template + void Geometry::sampleDir(const Real X, const Real Y, Real * const n) const { + //compute angle between detector/sample and sin/cos + const Real alpha = (Real(90) - sTlt + dTlt) * xtal::Constants::dg2rd;//angle between sample and detector is 90 with no tilts, increasing sample tilt decreases the angle, increasing camera tilt increases the angle + const Real sA = std::sin(alpha); + const Real cA = std::sqrt(Real(1) - sA * sA);//cos(alpha) + + //compute denomenator + const Real fx = (cX - X * w) * pX; + const Real fy = (cY - Y * h) * pY; + const Real den = std::sqrt(sDst * sDst + fx * fx + fy * fy); + + //compute normal + n[0] = (sDst * sA + fy * cA) / den; + n[1] = - fx / den; + n[2] = (sDst * cA - fy * sA) / den; + } + + //@brief : approximate the solid angle captured by a detector + //@param gridRes: side length of square lambert projection to grid over + //@return : fractional solid angle i.e [0,1] not [0,4*pi] + template + Real Geometry::solidAngle(size_t gridRes) const { + //loop over northern hemisphere + Real XY[2], xyz[3]; + size_t goodPoints = 0; + for(size_t j = 0; j <= gridRes; j++) {//loop over rows of square projection + XY[1] = Real(j) / gridRes; + for(size_t i = 0; i <= gridRes; i++) {//loop over columns of square projection + XY[0] = Real(i) / gridRes; + square::lambert::squareToSphere(XY[0], XY[1], xyz[0], xyz[1], xyz[2]); + if(interpolatePixel(xyz)) ++goodPoints;//count this point if it falls on the detector + } + } + const size_t totalPoints = (gridRes * gridRes + (gridRes - 2) * (gridRes - 2));//total number of points in both hemispheres (don't double count equators) + return Real(goodPoints) / totalPoints;//count fraction of grid points on detector + } + + //@brief : create a new detector geometry by rescaling the pixels size while maintaining solid angle + //@param scale: rescaling factor, must lie in [0, min(w, h)] with values < 1 corresponding to increasing pixel density + //@note : this is analogous to continuous camera binning with e.g. scale = 4.0 corresponding to 4x4 camera binning + template + Geometry Geometry::rescale(const Real scale) const { + const size_t wNew = (size_t) std::round(Real(w) / scale); + const size_t hNew = (size_t)std::round(Real(h) / scale); + if(wNew == 0 || hNew == 0) throw std::runtime_error("cannot rescale detector to less than 1 pixel"); + return rescale(wNew, wNew); + } + + //@brief : create a new detector geometry by rescaling the pixels size while maintaining solid angle + //@param wNew: scaled detector width + //@param hNew: scaled detector height + template + Geometry Geometry::rescale(size_t wNew, const size_t hNew) const { + //copy everything and update size + Geometry geom(*this); + geom.w = wNew; + geom.h = hNew; + + //compute scale factor in x/y direction + const Real sx = Real(w) / geom.w; + const Real sy = Real(h) / geom.h; + + //rescale pixels so that the detector covers the same area + geom.pX *= sx; + geom.pY *= sy; + + //rescale the pattern center to account for the new pixel size + geom.cX /= sx; + geom.cY /= sy; + return geom; + } + + //@brief: compute quaternion to rotate detector such that is centered around the north pole + //@return: quaternion as wxyz + template + std::array Geometry::northPoleQuat() const { + const Real theta = (Real(90) - sTlt + dTlt) * emsphinx::Constants::pi / 180; + // return std::array({std::cos(theta/2), 0, std::sin(theta/2) * emsphinx::pijk, 0}); + return std::array({1, 0, 0, 0}); + } + + //@brief : compute the rescale factor required to make average detector pixel size the same as an average pixel on a square spherical grid + //@param dim: side length of square spherical grid to match pixel size to + //@return : geometry scale factor to make average pixel sizes the same + template + Real Geometry::scaleFactor(const size_t dim) const { + const size_t sqrPix = dim * dim * 2 - (dim - 1) * 4;//number of pixels in square grid + const size_t detPix = w * h;//number of pixels on detector + return std::sqrt(solidAngle(501) * sqrPix / detPix); + } + + //@brief : read (partial) detector geometry info from an EMsoft master pattern file + //@param grp: folder in h5 file to read from (e.g. "/NMLparameters/EBSDNameList/") + //@note : this doesn't get the sample tilt which is a monte carlo parameter + template + void Geometry::readEMsoft(H5::Group grp) { + float thetac, delta, xpc, ypc, L; + int32_t numsx, numsy, binning; + std::string maskpattern; + grp.openDataSet("thetac" ).read(&thetac , H5::PredType::NATIVE_FLOAT); + grp.openDataSet("delta" ).read(&delta , H5::PredType::NATIVE_FLOAT); + grp.openDataSet("xpc" ).read(&xpc , H5::PredType::NATIVE_FLOAT); + grp.openDataSet("ypc" ).read(&ypc , H5::PredType::NATIVE_FLOAT); + grp.openDataSet("L" ).read(&L , H5::PredType::NATIVE_FLOAT); + grp.openDataSet("numsx" ).read(&numsx , H5::PredType::NATIVE_INT32); + grp.openDataSet("numsy" ).read(&numsy , H5::PredType::NATIVE_INT32); + grp.openDataSet("binning" ).read(&binning , H5::PredType::NATIVE_INT32); + grp.openDataSet("maskpattern").read( maskpattern, H5::StrType(0, H5T_VARIABLE), H5::DataSpace(H5S_SCALAR)); + cameraTilt(thetac); + cameraSize(numsx, numsy, delta); + patternCenter(xpc, ypc, L); + if(1 != binning) *this = bin(binning); + if (0 == maskpattern.compare("y")) maskPattern(true ); + else if(0 == maskpattern.compare("n")) maskPattern(false); + else throw std::runtime_error("unknown maskpattern option '" + maskpattern + "'"); + } + + //////////////////////////////////////////////////////////////////////// + // BackProjector // + //////////////////////////////////////////////////////////////////////// + + template + BackProjector::Constants::Constants(Geometry geo, const size_t dim, const Real fct, Real const * const qu) : + sclr(geo.w, geo.h, geo.scaleFactor(dim) * fct, fft::flag::Plan::Patient),//we're going to be doing lots of + flip(geo.flip) + { + //compute normals and solid angles of square legendre grid + std::vector xyz(dim * dim * 3); + square::legendre::normals(dim, xyz.data()); + std::vector omegaRing = square::solidAngles(dim, square::Layout::Legendre); + + //rescale detector + Geometry g = geo.rescale(sclr.wOut, sclr.hOut); + + //determine weights north hemisphere + image::BiPix p; + for(size_t i = 0; i < xyz.size() / 3; i++) {//loop over square legendre grid points + //get direction and apply rotatation + Real n[3]; + if(NULL != qu) { + xtal::quat::rotateVector(qu, xyz.data() + 3 * i, n); + } else { + std::copy(xyz.data() + 3 * i, xyz.data() + 3 * i + 3, n); + } + + //save interpolation weights if it hits the detector + if(g.interpolatePixel(n, &p, flip)) { + p.idx = i; + iPts.push_back(p); + omeg.push_back(omegaRing[square::ringNum(dim, i)]); + } + } + + //determine weights south hemisphere + for(size_t i = 0; i < xyz.size() / 3; i++) {//loop over square legendre grid points + //split into x and y indices + const size_t y = i / dim; + if(y == 0 || y+1 == dim) continue;//dont' double count equator + const size_t x = i - dim * y; + if(x == 0 || x+1 == dim) continue;//dont' double count equator + xyz[3*i+2] = -xyz[3*i+2];//move normal to southern hemisphere + + //get direction and apply rotatation + Real n[3]; + if(NULL != qu) { + xtal::quat::rotateVector(qu, xyz.data() + 3 * i, n); + } else { + std::copy(xyz.data() + 3 * i, xyz.data() + 3 * i + 3, n); + } + + //save interpolation weights if it hits the detector + if(g.interpolatePixel(n, &p, flip)) { + p.idx = i; + iPts.push_back(p); + omeg.push_back(omegaRing[square::ringNum(dim, i)]); + } + } + + //accumulate solid angle sums + omgW = std::accumulate(omeg.cbegin(), omeg.cend(), Real(0)); + omgS = 0; + for(size_t j = 0; j < dim; j++) { + for(size_t i = 0; i < dim; i++) { + const Real& o = omegaRing[square::ringNum(dim, j * dim + i)]; + const bool eq = j == 0 || i == 0 || j == dim-1 || i == dim-1;//are we on the equator + omgS += o; + if(!eq) omgS += o;//don't double count equator + } + } + } + + //@brief : construct a back projector + //@param geo: detector geometry to back project from + //@param dim: side length of square legendre grid to back project onto + //@param fct: additional scale factor for detector resizing + //@param qu : quaternion to rotate detector location by + template + BackProjector::BackProjector(Geometry geo, const size_t dim, const Real fct, Real const * const qu) : + pLut(std::make_shared(geo, dim, fct, qu)), + rPat(std::max(geo.w * geo.h, pLut->sclr.wOut * pLut->sclr.hOut)), + sWrk(pLut->sclr.allocateWork()), + iVal(pLut->iPts.size()) {} + + //@brief : unproject a pattern from the detector to a square grid + //@param pat: pattern to back project to unit sphere + //@param sph: location to write back projected pattern + //@return : sqrt(integral of pat^2) (properly weighted on the sphere), 0 if sum == false + //@param iq : location to write pattern image quality (NULL to skip computation) + template + Real BackProjector::unproject(Real * const pat, Real * const sph, Real * const iq) { + //rescale pattern + Real vIq = pLut->sclr.scale(pat, rPat.data(), sWrk.data(), true, 0, NULL != iq);//rescale + if(NULL != iq) *iq = vIq;//save iq value if needed + + //interpolate pattern intensities and compute weighted mean + image::BiPix const * const pIpt = pLut->iPts.data(); + for(size_t i = 0; i < pLut->iPts.size(); i++) iVal[i] = pIpt[i].interpolate(rPat.data());//determine interpolated values + const Real mean = std::inner_product(iVal.cbegin(), iVal.cend(), pLut->omeg.cbegin(), Real(0)) / pLut->omgW;//compute weighted average + + //make mean zero and compute weighted standard deviation + Real stdev = 0; + for(size_t i = 0; i < iVal.size(); i++) { + iVal[i] -= mean; + stdev += iVal[i] * iVal[i] * pLut->omeg[i]; + } + stdev = std::sqrt(stdev / pLut->omgW); + + if(Real(0) == stdev) {//the back projected image had no contrast + //copy to output grid making value uniformly 1 + for(size_t i = 0; i < pLut->iPts.size(); i++) sph[pIpt[i].idx] = Real(1); + return 0; + } else { + //make standard deviation 1 + for(size_t i = 0; i < iVal.size(); i++) iVal[i] /= stdev; + + //copy to output grid making mean 0 + for(size_t i = 0; i < pLut->iPts.size(); i++) sph[pIpt[i].idx] = iVal[i]; + + //compute sum if needed + static const Real var = std::sqrt(pLut->omgW / pLut->omgS * emsphinx::Constants::pi * Real(4));//standard deviation within window (since we normalized to 1) + return var; + } + + } + + //@brief : build a mask of spherical pixels covered by the detector + //@param sph: location to write mask + template + void BackProjector::mask(Real * const sph) { + for(const image::BiPix& p: pLut->iPts) sph[p.idx] = 1; + } + }//ebsd + +}//emsphinx + +#endif//_DETECTOR_H_ diff --git a/include/modality/ebsd/idx.hpp b/include/modality/ebsd/idx.hpp new file mode 100644 index 0000000..9da4970 --- /dev/null +++ b/include/modality/ebsd/idx.hpp @@ -0,0 +1,509 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _EBSD_IDX_H_ +#define _EBSD_IDX_H_ + +#define MINIZ_NO_STDIO +#define MINIZ_NO_TIME +#define MINIZ_NO_ZLIB_APIS +#include "miniz/miniz.c" + +#include + +//@brief : write a gray or rgb image to a png +//@param im : image to write +//@param w : image width +//@param h : image height +//@param samp: samples per pixel (1 for gray, 3 for rgb) +//@param name: file name to write to (*.png) +void writePng(uint8_t* im, const size_t w, const size_t h, const size_t samp, std::string name) { + //convert to png in memory + size_t pngSize = 0; + const mz_uint compr = MZ_BEST_COMPRESSION;//compression level [0,10] + const mz_bool flip = MZ_FALSE;//flip the image? + void *pPNG_data = tdefl_write_image_to_png_file_in_memory_ex((void*)im, (int)w, (int)h, (int)samp, &pngSize, compr, flip); + if(!pPNG_data) throw std::runtime_error("failed to create PNG image"); + + //write to file + std::ofstream os(name, std::ios::out | std::ios::binary); + os.write((char*)pPNG_data, pngSize); + mz_free(pPNG_data);//cleanup memory allocated by png creation +} + +#include +#include +#include + +#include "util/threadpool.hpp" +#include "util/image.hpp" +#include "xtal/orientation_map.hpp" +#include "modality/ebsd/pattern.hpp" +#include "modality/ebsd/nml.hpp" +#include "modality/ebsd/imprc.hpp" +#include "modality/ebsd/detector.hpp" +#include "idx/indexer.hpp" + +namespace emsphinx { + + namespace ebsd { + + template + struct IndexingData { + private: + //@brief : initialize indexing + //@param nml : namelist to initailze with + //@param pIpf: location to write ipf colorsnamelist to initailze with + void initialize(Namelist& nml, char * pIpf); + + public: + + IndexingData(IndexingData const&) = delete; + IndexingData& operator=(IndexingData const&) = delete; + + IndexingData(Namelist& nml, char * pIpf = NULL) {initialize(nml, pIpf);} + + std::vector< MasterSpectra > phases ;//master patterns to index against + std::shared_ptr pat ;//raw patterns to index + std::vector< xtal::OrientationMap > om ;//orientation maps to index into (1 per phase + 1 combined for multi phase) + std::vector idxMask ;//mask of point to index + Geometry geom ;//detector geometry + std::vector< std::unique_ptr< emsphinx::Indexer > > indexers ;//actual indexers + std::vector< std::vector< char > > patBufs ;//work space for patterns + std::function workItem ;//indexing work item + Namelist namelist ; + + size_t numIdx ;//number of point to index + std::atomic idxCtr ;//indexed pattern counter + size_t threadCount;//number of worker threads + std::vector< char > ipfBuf ;//ipf map + + //@brief : save results + //@param s: start time + //@param e: end time + //@param t: total time in s + void save(time_t& s, time_t& e, double t); + }; + + //@brief : templated work item for an ebsd indexing run + //@param om : orientation map(s) to index into [1 map for each result to save] + //@param pat: patterns to index + //@param idx: indexers + //@param buf: location to extract patterns for each thread + //@param cnt: batch size in # of patterns + //@param msk: indexing bitmask (0x01 to index, 0x02 to refine, 0x03 for both) + //@param ctr: completed pattern counter + //@param ipf: location to write ipf map + template + std::function ebsdWorkItem( + std::vector > & om ,//map is shared (each pixel is accessed by only 1 thread) + std::shared_ptr pat,//patterns are shared (thread safe) + std::vector< std::unique_ptr< emsphinx::Indexer > >& idx,//1 indexer per thread + std::vector< std::vector< char > > & buf,//1 buffer per thread + const size_t cnt,//1 buffer per thread + char const * const msk,//indexing bitmask + std::atomic& ctr,//counter for indexed pattern count + char * const ipf //ipf map + ); + + + //@brief: helper to compute image quality in parallel + template + class ThreadedIqCalc { + ThreadPool pool ;//worker threads + std::vector< std::shared_ptr< image::ImageQualityCalc > > calcs;//image quality calculators + std::vector< std::vector > buffs;//pattern read buffers + std::vector< std::vector > dBufs;//pattern conversion buffers + std::atomic cmplt;//count of completed patterns + + public: + //@brief : construct a threaded IQ calculator + //@param nThd: number of worker threads + ThreadedIqCalc(const size_t nThd = std::max(ThreadPool::Concurrency() / 2, 1)) : pool(nThd), calcs(nThd), buffs(nThd), dBufs(nThd) {cmplt.store(-1);} + + //@brief: destructor cancels work immediatley (you must keep the object in scope long enough for completion) + ~ThreadedIqCalc() {pool.clear();} + + //@brief : set the pattern file and start computing (non-blocking) + //@param pat: patterns to compute for + //@param iq : vector to start writing image quality + //@param upd: callback function for updates (called once when calculations have started and once when all calculations are queued) + void start(std::shared_ptr pat, std::vector& iq, std::function upd = NULL); + + //@brief : wait for the thread pool to finish + //@param timeout: how long to wait + //@return : number of completed patterns (-1 for unstarted) + template > + int64_t wait(std::chrono::duration timeout) const {pool.waitAll(timeout); return cmplt.load();} + }; + + //@brief : initialize indexing + //@param nml : namelist to initailze with + //@param pIpf: location to write ipf colorsnamelist to initailze with + template + void IndexingData::initialize(Namelist& nml, char * pIpf) { + namelist = nml; + nml.writeFileHeader();//save meta data up front + + //read master patterns + for(const std::string& file : nml.masterFiles) { + phases.push_back(MasterSpectra(file));//compute SHT of master patterns once + phases.back().resize(nml.bw); + } + for(size_t i = 1; i < phases.size(); i++) { + if(phases.front().getSig() != phases[i].getSig() || phases.front().getKv() != phases[i].getKv()) throw std::runtime_error("all master patterns must have the same tilt and kV"); + } + + //add psuedo-symmetry + size_t numMatches = 1; + if(!nml.pSymFile.empty()) { + if(1 != phases.size()) throw std::runtime_error("psuedo-symmetry files currently only supported for single phase indexing"); + phases[0].addPseudoSym(nml.pSymFile); + } + + //read patterns and sanity check number / size + pat = PatternFile::Read(nml.patFile, nml.patName, nml.patDims[0], nml.patDims[1]); + if(pat->numPat() < nml.scanDims[0] * nml.scanDims[1]) throw std::runtime_error("not enough patterns for scan"); + else if(pat->numPat() > nml.scanDims[0] * nml.scanDims[1]) { + // std::cout << "\n * * * * * * warning: " << pat->numPat() - nml.scanDims[0] * nml.scanDims[1] << " more pattern(s) than scan points * * * * * * \n\n"; + } + if(pat->width() != nml.patDims[0] || pat->height() != nml.patDims[1]) throw std::runtime_error("detector/pattern size mismatch"); + + //build mask and convert from 0/1 to 0/3 if needed (for old style indexing bitmask) + idxMask = nml.roi.buildMask(nml.scanDims[0], nml.scanDims[1]); + numIdx = std::count_if(idxMask.cbegin(), idxMask.cend(), [](const uint8_t& v){return v != 0;}); + if(0 == numIdx) { + std::fill(idxMask.begin(), idxMask.end(), 1); + numIdx = idxMask.size(); + } + if(nml.refine) { + for(char& v : idxMask) { + if(v == 1) v = 3; + } + } + + //read tilt from master pattern monte carlo data and build geometry + geom.sampleTilt(phases.front().getSig()); + geom.cameraTilt(nml.thetac); + geom.cameraSize(nml.patDims[0], nml.patDims[1], nml.delta); + if("EMsoft" == nml.ven) { + geom.patternCenter (nml.pctr[0], nml.pctr[1], nml.pctr[2]); + } else if("EDAX" == nml.ven || "tsl" == nml.ven) { + geom.patternCenterTSL (nml.pctr[0], nml.pctr[1], nml.pctr[2]); + } else if("Oxford" == nml.ven) { + geom.patternCenterOxford(nml.pctr[0], nml.pctr[1], nml.pctr[2]); + } else if("Bruker" == nml.ven || "tsl" == nml.ven) { + geom.patternCenterBruker(nml.pctr[0], nml.pctr[1], nml.pctr[2]); + } else throw std::runtime_error("unknown vendor for pattern center `" + nml.ven + "'"); + geom.maskPattern(nml.circRad == 0); + geom.flipPattern(pat->flipY()); + + //determine threading / batch values + threadCount = nml.nThread == 0 ? ThreadPool::Concurrency() : nml.nThread; + if(0 == nml.batchSize) nml.batchSize = (int32_t)Indexer::BatchEstimate(nml.bw, threadCount, numIdx); + + //create orientation map from input scan dimensions and add phases from master patterns + xtal::OrientationMap newOm(nml.scanDims[0], nml.scanDims[1], nml.scanSteps[0], nml.scanSteps[1]); + for(const MasterSpectra& mp : phases) newOm.phsList.push_back(mp.phase()); + newOm.calib.sTlt = phases.front().getSig(); + const size_t extraScans = 1 == phases.size() ? phases.front().pseudoSym().size() : 1;//do we need extra scans for psuedosymmetry or multi phase + om.assign(phases.size() + extraScans, newOm); + + //build image processor + std::unique_ptr< PatternProcessor > prc(new PatternProcessor()); + prc->setSize(geom.w, geom.h, nml.circRad, nml.gausBckg, nml.nRegions); + + //build back projector + const size_t gridDim = nml.bw + (nml.bw % 2 == 0 ? 3 : 2); + std::array quNp = geom.northPoleQuat(); + std::unique_ptr< BackProjector > prj(new BackProjector(geom, gridDim, std::sqrt(Real(2)), quNp.data())); + + //build cross correlators + std::vector< std::unique_ptr< sphere::PhaseCorrelator > > corrs; + if(nml.normed) { + //build a spherical harmonic transformer once + square::DiscreteSHT sht(gridDim, nml.bw, square::Layout::Legendre); + std::vector sph(gridDim * gridDim * 2, 0);//spherical grid + + //compute the SHT of the window function once + std::vector win(pat->numPix(), Real(1));//image of solid 1 + prj->unproject(win.data(), sph.data());//unproject window function onto sphere + std::vector< std::complex > mlm(nml.bw * nml.bw); + sht.analyze(sph.data(), mlm.data()); + + //now loop over the phases computing SHT(function^2) and building normalized correlators + std::vector< std::complex > flm2(nml.bw * nml.bw); + for(const MasterSpectra& p : phases) { + sht.synthesize(p.data(), sph.data());//reconstruct function on legendre grid + std::transform(sph.cbegin(), sph.cend(), sph.cbegin(), sph.begin(), std::multiplies());//square function + + sht.analyze(sph.data(), flm2.data());//compute SHT(function^2) + std::unique_ptr< sphere::NormalizedCorrelator > pCorr(new sphere::NormalizedCorrelator((int)nml.bw, p.data(), flm2.data(), p.mirror(), p.nFold(), mlm.data() ) );//build correlator + corrs.push_back( std::move( pCorr ) ); + } + } else { + //build unnormalized spherical cross correlators + for(const MasterSpectra& p : phases) { + std::shared_ptr< std::vector< std::complex > > flm = std::make_shared< std::vector< std::complex > >(p.data(), p.data() + nml.bw * nml.bw);//copy harmonics into shared pointer + std::unique_ptr< sphere::UnNormalizedCorrelator > pCorr(new sphere::UnNormalizedCorrelator((int)nml.bw, flm, p.mirror(), p.nFold() ) );//build correlator + corrs.push_back( std::move( pCorr ) ); + } + } + + //build a single indexer for each thread + std::unique_ptr > idx(new emsphinx::Indexer((int)nml.bw, prc->clone(), prj->clone(), corrs));//make a single indexer (good for 1 thread) + indexers.push_back(std::move(idx));//move original indexer onto + for(size_t i = 1; i < threadCount; i++) indexers.push_back( std::move(indexers.front()->clone()) );//duplicate n-1 times + + //build thread work item + idxCtr.store(0); + patBufs.assign(threadCount, std::vector(nml.batchSize * pat->imBytes())); + if(NULL == pIpf) { + ipfBuf.resize(nml.scanDims[0] * nml.scanDims[1] * 3); + pIpf = ipfBuf.data(); + } + switch(pat->pixelType()) {//templated lambda functions don't exist yet so we'll need to select the appropriate type here + case PatternFile::Bits::U8 : workItem = ebsdWorkItem(om, pat, indexers, patBufs, nml.batchSize, idxMask.data(), std::ref(idxCtr), pIpf); break; + case PatternFile::Bits::U16: workItem = ebsdWorkItem(om, pat, indexers, patBufs, nml.batchSize, idxMask.data(), std::ref(idxCtr), pIpf); break; + case PatternFile::Bits::F32: workItem = ebsdWorkItem(om, pat, indexers, patBufs, nml.batchSize, idxMask.data(), std::ref(idxCtr), pIpf); break; + case PatternFile::Bits::UNK: throw std::runtime_error("unsupported pattern pixel type"); + } + } + + //@brief : save results + //@param s: start time + //@param e: end time + //@param t: total time in s + template + void IndexingData::save(time_t& s, time_t& e, double t) { + //reopen HDF output + H5::H5File file(namelist.opath + namelist.dataFile, H5F_ACC_RDWR);//we made the file earlier + {//write indexing start / stop times + std::stringstream ss; + ss << std::put_time(std::localtime(&s), "%a %b %d %Y %T"); + std::string startStr = ss.str(); + ss.str(""); + ss << std::put_time(std::localtime(&e), "%a %b %d %Y %T"); + std::string endStr = ss.str(); + ss.str(""); + double rate = double(numIdx) / t; + file.openGroup("EMheader").createDataSet("StartTime", H5::StrType(0, H5T_VARIABLE), H5::DataSpace(H5S_SCALAR)).write(startStr, H5::StrType(0, H5T_VARIABLE)); + file.openGroup("EMheader").createDataSet("StopTime" , H5::StrType(0, H5T_VARIABLE), H5::DataSpace(H5S_SCALAR)).write(endStr , H5::StrType(0, H5T_VARIABLE)); + file.openGroup("EMheader").createDataSet("PatPerS" , H5::PredType::NATIVE_DOUBLE , H5::DataSpace(H5S_SCALAR)).write(&rate , H5::PredType::NATIVE_DOUBLE ); + } + + //choose IPF reference direction and hsl function + Real n[3] = {0, 0, 1}; + std::function h2r = xtal::sph2rgb; + + //save out vendor file and images if needed + std::vector ipfMap = om.front().ipfColor(n, h2r); + std::vector xcMap = image::to8Bit(om.front().metric.data(), om.front().metric.size()); + std::vector iqMap = image::to8Bit(om.front().imQual.data(), om.front().imQual.size()); + if(!namelist.vendorFile.empty()) om.front().write(namelist.opath + namelist.vendorFile); + if(!namelist.ipfName .empty()) writePng(ipfMap.data(), om.front().width, om.front().height, 3, namelist.opath + namelist.ipfName ); + if(!namelist.qualName .empty()) writePng(xcMap .data(), om.front().width, om.front().height, 1, namelist.opath + namelist.qualName); + + //write write all scans to hdf + size_t padNum = (size_t) ceil(std::log10(om.size()+1));//how many digits does the longest number take + for(size_t i = 0; i < om.size(); i++) { + //make maps + ipfMap = om.front().ipfColor(n, h2r); + xcMap = image::to8Bit(om.front().metric.data(), om.front().metric.size()); + iqMap = image::to8Bit(om.front().imQual.data(), om.front().imQual.size()); + + //build string prefix "0i_" + std::stringstream ss; + ss << std::setfill('0') << std::setw(padNum) << i + 1; + + //write scan data + om[i].name = "Match " + ss.str(); + hsize_t dims[3] = {om[i].height, om[i].width, 3}; + H5::Group grp = file.createGroup("Scan " + ss.str()); + om[i].writeH5(grp); + // H5IMmake_image_8bit(grp.getId(), "IPF Map", om[i].width, om[i].height, ipfMap.data());//this requires HL liberary + grp.createDataSet("IPF Map", H5::PredType::NATIVE_UINT8, H5::DataSpace(3, dims)).write(ipfMap.data(), H5::PredType::NATIVE_UINT8); + grp.createDataSet("XC Map" , H5::PredType::NATIVE_UINT8, H5::DataSpace(2, dims)).write(xcMap .data(), H5::PredType::NATIVE_UINT8); + grp.createDataSet("IQ Map" , H5::PredType::NATIVE_UINT8, H5::DataSpace(2, dims)).write(iqMap .data(), H5::PredType::NATIVE_UINT8); + } + + } + + //@brief : templated work item for an ebsd indexing run + //@param om : orientation map(s) to index into [1 map for each result to save] + //@param pat: patterns to index + //@param idx: indexers + //@param buf: location to extract patterns for each thread + //@param cnt: batch size in # of patterns + //@param msk: indexing bitmask (0x01 to index, 0x02 to refine, 0x03 for both) + //@param ctr: completed pattern counter + //@param ipf: location to write ipf map + template + std::function ebsdWorkItem( + std::vector > & om ,//map is shared (each pixel is accessed by only 1 thread) + std::shared_ptr pat,//patterns are shared (thread safe) + std::vector< std::unique_ptr< emsphinx::Indexer > >& idx,//1 indexer per thread + std::vector< std::vector< char > > & buf,//1 buffer per thread + const size_t cnt,//1 buffer per thread + char const * const msk,//indexing bitmask + std::atomic& ctr,//counter for indexed pattern count + char * const ipf //ipf map + ) { + //@brief : index a batch of pattern + //@param count: maximum number of patterns to index + //@param id : id of current thread + return [&om,pat,&idx,&buf,cnt,msk,&ctr,ipf](const size_t id){//work function + //choose IPF reference direction and hsl function + Real n[3] = {0, 0, 1}, nx[3], rgb[3]; + std::function h2r = xtal::sph2rgb; + + //extract patterns and get pointer to first pattern + const std::vector indices = pat->extract(buf[id].data(), cnt); + const size_t bytes = pat->imBytes(); + char const * ptr = buf[id].data(); + + //loop over patterns indexing + std::vector< emsphinx::Result > res(om.size());//we need a result for each map + for(const size_t i : indices) {//loop over indices to index (may not be contiguous or in order) + if(0x00 != msk[i]) {//do we need to index and/or refine this point? + if(0x01 & msk[i]) {//reindexing requested + const bool ref = 0x02 & msk[i];//should we refine? + try { + idx[id]->indexImage((TPix*)ptr, res.data(), res.size(), ref);//index pattern + for(size_t j = 0; j < res.size(); j++) { + std::copy(res[j].qu, res[j].qu+4, om[j].qu[i].data());//save orientation + om[j].metric[i] = res[j].corr ;//save cross correlation + om[j].imQual[i] = res[j].iq ;//image quality + om[j].phase [i] = (uint_fast8_t)res[j].phase;//save phase + } + std::fill(ipf + 3 * i, ipf + 3 * i + 3, 0); + xtal::quat::rotateVector(res[0].qu, n, nx); + if (-1 == res[0].phase) { + std::fill(rgb, rgb + 3, Real(0)); + } else { + om[0].phsList[res[0].phase].pg.ipfColor(nx, rgb, h2r); + } + std::transform(rgb, rgb+3, ipf + 3 * i, [](const Real& v){return (char) std::round(v * 255);}); + } catch (std::exception&) {// e) { + // std::cout << i << ": " << e.what() << '\n';//don't let one exception stop everything + for(size_t j = 0; j < res.size(); j++) { + om[j].qu[i].w = 1; + om[j].qu[i].x = om[j].qu[i].y = om[j].qu[i].z = 0; + om[j].metric[i] = 0; + om[j].imQual[i] = 0; + om[j].phase [i] = (uint_fast8_t)-1; + } + std::fill(ipf + 3 * i, ipf + 3 * i + 3, 0); + } + } else if(0x02 & msk[i]) {//only refinement requested + try { + //this currently only uses a single result but it could be modified + res[0].phase = om[0].phase[i]; + std::copy(om[0].qu[i].data(), om[0].qu[i].data()+4, res[0].qu);//copy original orientation + idx[id]->refineImage((TPix*)ptr, res[0]);//refine orientation using pattern + std::copy(res[0].qu, res[0].qu+4, om[0].qu[i].data());//save orientation + om[0].metric[i] = res[0].corr ;//save cross correlation + om[0].imQual[i] = res[0].iq ;//image quality + } catch (std::exception&) {// e) { + // std::cout << i << ": " << e.what() << '\n';//don't let one exception stop everything + } + } + ctr++;//increment indexed pattern count + } + ptr += bytes;//move to next pattern + } + }; + } + + //@brief : set the pattern file and start computing (non-blocking) + //@param pat: pattern file to set + //@param iq : vector to start writing image quality + //@param upd: callback function for updates (called once when calculations have started and once when all calculations are queued) + template + void ThreadedIqCalc::start(std::shared_ptr pat, std::vector& iq, std::function upd) { + //build calculators + const size_t batSz = 10;//arbitrarily set batch size for now + calcs.front() = std::make_shared< image::ImageQualityCalc >(pat->width(), pat->height());//build a single image calculator + for(size_t i = 1; i < calcs.size(); i++) calcs[i] = std::make_shared< image::ImageQualityCalc >(*calcs.front());//copy the rest + for(size_t i = 0; i < calcs.size(); i++) {//allocate buffers + buffs[i].resize(batSz * pat->imBytes ()); + dBufs[i].resize( pat->numPix ()); + } + + //compute number of batches + const size_t totNm = pat->numPat(); + size_t batches = totNm / batSz;//how many batches are needed + if(batches * batSz < totNm) ++batches;//extra batch for leftovers + + //build a work item to process a single batch + std::function work = [batSz,pat,&iq,this](const size_t id) { + //extract a batch of patterns and get pointer to first pattern + const std::vector indices = pat->extract(this->buffs[id].data(), batSz); + const size_t bytes = pat->imBytes(); + char const * ptr = this->buffs[id].data(); + + //loop over patterns computing image quality + for(const size_t i : indices) {//loop over indices to index (may not be contiguous or in order) + switch(pat->pixelType()) {//convert pattern to double + case PatternFile::Bits::UNK: std::fill(this->dBufs[id].begin(), this->dBufs[id].end(), 0); break; + case PatternFile::Bits::U8 : std::copy((uint8_t const*)ptr, (uint8_t const*)(ptr+bytes), this->dBufs[id].begin()); break; + case PatternFile::Bits::U16: std::copy((uint16_t const*)ptr, (uint16_t const*)(ptr+bytes), this->dBufs[id].begin()); break; + case PatternFile::Bits::F32: std::copy((float const*)ptr, (float const*)(ptr+bytes), this->dBufs[id].begin()); break; + } + iq[i] = this->calcs[id]->compute(this->dBufs[id].data());//compute image quality + (this->cmplt)++;//increment count + ptr += bytes;//move to next pattern + } + }; + + //queue work + cmplt.store(0);//we haven't finished any patterns + iq.resize(totNm);//make sure there is space to write outputs + if(NULL != upd) upd(); + for(size_t i = 0; i < batches; i++) {//loop over batches + const size_t start = i * batSz;//first pattern + const size_t end = std::min(start + batSz, pat->numPat());//last pattern + pool.schedule(std::bind(work, std::placeholders::_1));//queue computatino + } + if(NULL != upd) upd(); + } + + }//ebsd + +}//emsphinx + + + +#endif//_EBSD_IDX_H_ + diff --git a/include/modality/ebsd/imprc.hpp b/include/modality/ebsd/imprc.hpp new file mode 100644 index 0000000..a0ad5fe --- /dev/null +++ b/include/modality/ebsd/imprc.hpp @@ -0,0 +1,197 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _EBSD_IM_PRC_ +#define _EBSD_IM_PRC_ + +#include "util/gaussian.hpp" +#include "util/ahe.hpp" + +namespace emsphinx { + + namespace ebsd { + + //@brief: class to hold pattern processing details + template + class PatternProcessor : public ImageProcessor { + size_t nPix ; + bool doBkg; + bool doAhe; + bool msk ;//is there a mask (or are all pixels good) + gaussian::BckgSub2D bkg ; + AdaptiveHistogramEqualizer ahe ; + std::vector work ;//work space for 8 bit conversion (this could be eliminated with some reordering of operations, but it doesn't seem worth it) + + public: + //@brief: set processing parameters + //@param w: image width in pixels + //@param h: image height in pixels + //@param r: circular mask radius in pixels (-1 for no mask, 0 to automatically size mask) + //@param b: true to use background subtraction, false otherwise + //@param n: adaptive histogram equalization nregions + void setSize(const size_t w, const size_t h, const int r, const bool b, const size_t n); + + //@brief : process an 8 bit image in place (background subtract and/or AHE) + //@param im: image to process (modified) + void process(uint8_t * const im); + + //@brief : process an image out of place (background subtract and/or AHE) + //@param im : image to process + //@param buf: location to write floating point processed image + template + void process(TPix const * const im, Real * const buf); + void process(uint8_t const * const im, Real * const buf) {return process(im, buf);} + void process(uint16_t const * const im, Real * const buf) {return process(im, buf);} + void process(float const * const im, Real * const buf) {return process(im, buf);} + + //@brief : get background subtraction mask + //@return: read only pointer to mask (1/0 for inside/outside mask) + char const * getMask() const {return bkg.msk->data();} + + //@brief : get size of target image to process + //@return: size of input image in pixels + size_t numPix() const {return nPix;} + + //@brief : get a copy of the stored image processor + //@return: unique pointer to copy of current processor + std::unique_ptr > clone() const {return std::unique_ptr(new PatternProcessor(*this));} + }; + + }//ebsd + +}//emsphinx + + +#include + +namespace emsphinx { + + namespace ebsd { + + //@param w: image width in pixels + //@param h: image height in pixels + //@param r: circular mask radius in pixels (-1 for no mask, 0 to automatically size mask) + //@param b: true to use background subtraction, false otherwise + //@param n: adaptive histogram equalization nregions + template + void PatternProcessor::setSize(const size_t w, const size_t h, const int r, const bool b, const size_t n) { + nPix = w * h; + + doBkg = b ; + //always build background subtractor (for mask) + if(-1 == r) {//no mask + msk = false; + bkg = gaussian::BckgSub2D(w, h); + } else {//circular mask + msk = true; + if(r == 0)//automatically determine size + bkg = gaussian::BckgSub2D::CircMask((int)w, (int)h); + else//specify size + bkg = gaussian::BckgSub2D::CircMask((int)w, (int)h, r); + } + + //only build AHE calculator if needed + doAhe = n > 0; + if(doAhe) { + ahe.setSize(w, h, n, n); + } else { + ahe = AdaptiveHistogramEqualizer(); + } + + //allocate 8 bit conversion work space if needed + if(doBkg) {//background subtraction (+ potentially ahe) + if(doAhe) {//convert background subtracted floating point image to 8 bit + work.resize(w * h); + } else { + work.clear(); + } + } else if(doAhe) {//only histogram equalization + work.resize(w * h); + } else {//no image processing + work.clear(); + } + + + } + + //@brief : process an image in place (background subtract and/or AHE) + //@param im: image to process (modified) + template + void PatternProcessor::process(uint8_t * const im) { + if(doBkg) { + bkg.fit(im);//determine background + bkg.subtract(im);//in place background subtract + } + if(doAhe) { + ahe.equalize(im, msk ? getMask() : NULL); + } + } + + //@brief : process an image out of place (background subtract and/or AHE) + //@param im : image to process + //@param buf: location to write floating point processed image + template + template + void PatternProcessor::process(TPix const * const im, Real * const buf) { + if(doBkg) {//background subtraction (+ potentially ahe) + bkg.fit(im);//determine background + bkg.subtract(im, buf);//in place background subtract + + if(doAhe) {//convert background subtracted floating point image to 8 bit + std::pair minMax = std::minmax_element(buf, buf + nPix); + const Real vMin = *minMax.first; + const Real factor = Real(255) / (*minMax.second - vMin); + std::transform(buf, buf + nPix, work.data(), [vMin, factor](const Real& v){return (uint8_t)(factor * (v - vMin) + Real(0.5));}); + ahe.equalize(work.data(), buf, msk ? getMask() : NULL); + } + } else if(doAhe) {//only histogram equalization + if(std::is_same::value) {//already 8 bit + ahe.equalize((uint8_t const *) im, buf, msk ? getMask() : NULL); + } else {//need to convert input to 8 bit + std::pair minMax = std::minmax_element(im, im + nPix); + const TPix vMin = *minMax.first; + const Real factor = Real(255) / (*minMax.second - vMin); + std::transform(im, im + nPix, work.data(), [vMin, factor](const TPix& v){return (uint8_t)(factor * (v - vMin) + Real(0.5));}); + ahe.equalize(work.data(), buf, msk ? getMask() : NULL); + } + } else {//no image processing + std::transform(im, im + nPix, buf, [](const TPix& v){return (Real)v;});//just copy to output + } + } + + }//ebsd + +}//emsphinx + +#endif//_EBSD_IM_PRC_ diff --git a/include/modality/ebsd/nml.hpp b/include/modality/ebsd/nml.hpp new file mode 100644 index 0000000..9d225ee --- /dev/null +++ b/include/modality/ebsd/nml.hpp @@ -0,0 +1,584 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _EBSD_NML_ +#define _EBSD_NML_ + +#include +#include + +#include "idx/roi.h" + +namespace emsphinx { + + namespace ebsd { + + //@brief: encapsulation of all the parameters needed for EBSD indexing + struct Namelist { + + std::string ipath ;//input file path + std::string patFile ;//pattern file [hdf, up1, up2, ebsp, data] + std::string patName ;//path to dataset within pattern file (hdf only) + std::vector masterFiles ;//master pattern files + std::string pSymFile ;//file for psuedosymmetry + + int32_t patDims[2] ;//binned width and height of ebsd patterns + int32_t circRad ;//circular mask radius + bool gausBckg ;//should a gaussian background subtraction be applied + int32_t nRegions ;//AHE tile count + + double delta ;//unbinned pixel size in microns + std::string ven ;//pattern center type + double pctr[3] ;//pattern center + double thetac ;//camera tilt in degrees + + int32_t scanDims[2] ;//scan width and height in pixels + double scanSteps[2] ;//pixel width and height in microns + std::string scanFile ;//scan file to read dimensions from + std::string scanName ;//scan file to read pixel size from + RoiSelection roi ;//indexing region of interest + + int32_t bw ;//spherical harmonic bandwidth to index with + bool normed ;//should normalized cross correlation be used instead of unnormalized + bool refine ;//should newton's method refinement be used instead of subpixel interpolation + int32_t nThread ; + int32_t batchSize ; + + std::string opath ;//output file path + std::string dataFile ;//output HDF file + std::string vendorFile ;//output ang or ctf + std::string ipfName ;//output ipf map + std::string qualName ;//output confidence map (cross correlation) + + std::string namelistFile ;//namelist file used for building (actual file contents) + + //@brief: clear inputs files + void clearInputs() {ipath = patFile = patName = pSymFile = ""; masterFiles.clear();} + + //@brief: clear the pattern file image processing + void clearImPrc() {patDims[0] = patDims[1] = -1; circRad = -1; gausBckg = false; nRegions = 0;} + + //@brief: clear the pattern center + void clearPctr() {ven = "EMsoft"; pctr[0] = pctr[1] = pctr[2] = NAN; thetac = delta = NAN;} + + //@brief: clear the scan dimensions + void clearDims() {scanDims[0] = scanDims[1] = -1; scanSteps[0] = scanSteps[1] = NAN; scanFile = scanName = ""; roi.clear();} + + //@brief: clear indexing parameters + void clearIdxPrm() {bw = -1; normed = false; refine = false; nThread = 0; batchSize = 0;} + + //@brief: clear outputs + void clearOutputs() {opath = dataFile = vendorFile = ipfName = qualName = "";} + + //@brief: initialize / reset values with defaults + void clear(); + + //@brief: fill namelist with reasonable defaults + void defaults(); + + //@biref: construct an emty namelist + Namelist() {clear();} + + //@brief : parse indexing values from a namelist file + //@param nml: file to parse (actual contents) + //@return : warning string (list of unused namelist values) + std::string from_string(std::string nml); + + //@brief : convert a namelist to a string + //@param nml: namelist name (for fortran) + //@return : namelist file string + std::string to_string(std::string nml = "EMSphInx") const; + + //@brief : set pattern file and parse as much information as possible + //@param file: file to set + //@param aux : path to h5 dataset if needed + //@note : for h5 files searches for a scan that the dataset belongs to + //@note : for up1/up2 files searches for a matching .ang + //@note : for ebsp files searches for a matching ctf + void getPatDims(std::string file, std::string aux); + + //@brief : search for a scan file based on the current scan file name + //@param pIq: [optional] pointer to location to store image quality map from scan file + //@param pCi: [optional] pointer to location to indexing quality map from scan file + //@return : true if additional information was parsed, false otherwise + //@note : may parse pattern center, camera tilt, scan dimensions, and pixel size + bool findScanFile(std::vector* pIq = NULL, std::vector* pCi = NULL); + + //@brief : read a scan file with a known file name (scanFile/scanName) + //@param pIq: [optional] pointer to location to store image quality map from scan file + //@param pCi: [optional] pointer to location to indexing quality map from scan file + //@return : true if additional information was parsed, false otherwise + //@note : may parse pattern center, camera tilt, scan dimensions, and pixel size + bool readScanFile(std::vector* pIq = NULL, std::vector* pCi = NULL); + + //@brief: create the output data file and write header data to it + void writeFileHeader() const; + }; + + }//ebsd + +}//emsphinx + +#include + +#include "util/nml.hpp" +#include "modality/ebsd/pattern.hpp" +#include "util/sysnames.hpp" +#include "xtal/orientation_map.hpp" + +namespace emsphinx { + + namespace ebsd { + + //@brief: initialize / reset values with defaults + void Namelist::clear() { + clearInputs (); + clearImPrc (); + clearPctr (); + clearDims (); + clearIdxPrm (); + clearOutputs(); + } + + void Namelist::defaults() { + clear(); + ipath = "" ; + patFile = "scan.h5" ; + patName = "Scan 1/EBSD/Data/Pattern"; + masterFiles = std::vector(1, "master.h5"); + pSymFile = "" ; + patDims[0] = 640; patDims[1] = 480 ; + circRad = -1 ; + gausBckg = false ; + nRegions = 10 ; + delta = 50.0 ; + ven = "EMsoft" ; + pctr[0] = 0.0 ; + pctr[1] = 0.0 ; + pctr[2] = 15000.0 ; + thetac = 10.0 ; + scanDims[0] = 256 ; + scanDims[1] = 256 ; + scanSteps[0] = 1.0 ; + scanSteps[1] = 1.0 ; + roi.clear(); + bw = 68 ; + normed = true ; + refine = true ; + nThread = 0 ; + batchSize = 0 ; + opath = "" ; + dataFile = "SphInx_Scan.h5" ; + vendorFile = "reindexed.ang" ; + ipfName = "ipf.png" ; + qualName = "qual.png" ; + } + + //@brief : parse indexing values from a namelist file + //@param nml: file to parse (actual contents) + //@return : warning string (list of unused namelist values) + std::string Namelist::from_string(std::string nml) { + //start by wrapping input as stream and parsing with namelist reader + clear(); + namelistFile = nml;//save a copy of the file + std::istringstream iss(nml); + nml::NameList nameList; + nameList.read(iss); + + //parse inputs first + try { ipath = nameList.getString ("ipath" );} catch (...) {ipath .clear();}//if ipath isn't found we'll just use cwd + try { pSymFile = nameList.getString ("psymfile" );} catch (...) {pSymFile.clear();}//if pSymFile isn't found no operators will be checked + masterFiles = nameList.getStrings("masterfile"); + patFile = ipath + nameList.getString("patfile" ); + const bool h5Pat = H5::H5File::isHdf5(patFile); + if(h5Pat) patName = nameList.getString("patdset" ); + if(!pSymFile.empty()) patFile = ipath + patFile; + for(std::string& str : masterFiles) str = ipath + str; + + //parse scan dimensions (before pattern center since this will overwrite values) + try{ + scanFile = ipath + nameList.getString("scandims");//try to get scan dims as a file name + const bool h5Scn = H5::H5File::isHdf5(patFile); + if(h5Scn) scanName = nameList.getString("scanname"); + if(!readScanFile()) throw std::runtime_error("failed to read scan dimensions from " + scanFile); + pctr[0] = pctr[1] = pctr[2] = thetac = NAN; + } catch (std::runtime_error&) { + //if we didn't get a file name check for scan dimensions + std::vector dims = nameList.getDoubles("scandims"); + if(3 != dims.size() && 4 != dims.size()) throw std::runtime_error("expected a filename or dimensions + resolution for 'scandims' in namelist"); + + //sanity check scan dimensions + if(dims[0] != (uint32_t) dims[0] || dims[1] != (uint32_t) dims[1]) throw std::runtime_error("scan dimensinos must be non-negative integers"); + scanDims [0] = (uint32_t) dims[0]; + scanDims [1] = (uint32_t) dims[1]; + scanSteps[0] = dims[2]; + scanSteps[1] = dims.back(); + } + roi.from_string(nameList.getString("roimask")); + + //parse pattern info + { + std::vector dims = nameList.getInts("patdims"); + if(2 != dims.size()) throw std::runtime_error("patdims must be 2 elements"); + patDims[0] = dims[0]; patDims[1] = dims[1]; + } + circRad = nameList.getInt ("circmask"); + gausBckg = nameList.getBool("gausbckg"); + nRegions = (size_t) nameList.getInt ("nregions");//adaptive histogram grid resolution + + //parse pattern center + delta = nameList.getDouble("delta" ); + thetac = nameList.getDouble("thetac"); + ven = nameList.getString ("vendor" ); + { + std::vector ctr = nameList.getDoubles("pctr" ); + if(3 != ctr.size()) throw std::runtime_error("pctr must be 3 elements"); + pctr[0] = ctr[0]; pctr[1] = ctr[1]; pctr[2] = ctr[2]; + } + if(!("EMsoft" == ven || + "EDAX" == ven || + "tsl" == ven || + "Oxford" == ven || + "Bruker" == ven || + "tsl" == ven )) throw std::runtime_error("unknown vendor for pattern center `" + ven + "'"); + + //parse indexing parameters + bw = (size_t) nameList.getInt ("bw" );//what bandwidth should be used, if 2*bw-1 is product of small primes it is a good candidate for speed (fft is significant fraction of time): 32,38,41,53,63,68,74,88,95,113,123,158 + normed = (size_t) nameList.getBool("normed" );//should normalized or unnormalized cross correlation be used + refine = (size_t) nameList.getBool("refine" );//should refinement be used + nThread = (size_t) nameList.getInt ("nthread" ); + batchSize = (size_t) nameList.getInt ("batchsize");//number of patterns per work item (should be large enough that the task is significant but small enough that there are enough jobs for load balancing) + + //parse outputs + try {opath = nameList.getString("opath" );} catch (...) {opath .clear();}//if ipath isn't found we'll just use cwd + dataFile = nameList.getString("datafile" ); + try {vendorFile = nameList.getString("vendorfile");} catch (...) {vendorFile.clear();}//if vendorfile isn't found we won't make an vendor file + try {ipfName = nameList.getString("ipfmap" );} catch (...) {ipfName .clear();}//if ipfName isn't found we won't make an ipf map + try {qualName = nameList.getString("qualmap" );} catch (...) {qualName .clear();}//if qualName isn't found we won't make a quality map + + //check for unused inputs + if(!nameList.fullyParsed()) return nameList.unusedTokens(); + return ""; + } + + //@brief : convert a namelist to a string + //@param nml: namelist name (for fortran) + //@return : namelist file string + std::string Namelist::to_string(std::string nml) const { + std::ostringstream ss; + ss << " &" << nml << "\n";//this if for the fortran version + ss << "!#################################################################\n"; + ss << "! Input Files\n"; + ss << "!#################################################################\n"; + ss << "\n"; + ss << "! input path, empty for current working directory\n"; + ss << " ipath = '" << ipath << "',\n";//ignored for fortran version + ss << "\n"; + ss << "! raw pattern file (relative to ipath) [can be up1, up2, or hdf5]\n"; + ss << " patfile = '" << patFile << "',\n"; + ss << "\n"; + ss << "! h5 path of raw pattern (ignored for non hdf5 patfile)\n"; + ss << " patdset = '" << patName << "',\n"; + ss << "\n"; + ss << "! master pattern with phases to index (relative to ipath)\n"; + ss << " masterfile = "; for(const std::string& str : masterFiles) ss << '\'' << str << "', "; ss << '\n'; + ss << "\n"; + ss << "! file with list of pseudo symmetric rotations to check (or '' for no psym check)\n"; + ss << " psymfile = '" << pSymFile << "',\n"; + ss << "\n"; + ss << "\n"; + ss << "!#################################################################\n"; + ss << "! Pattern Processing\n"; + ss << "!#################################################################\n"; + ss << "\n"; + ss << "! number of CCD pixels along x and y\n"; + ss << " patdims = " << patDims[0] << ", " << patDims[1] << ",\n"; + ss << "\n"; + ss << "! should a circular mask be applied (-1 for no mask, 0 for largest inscribed circle, >0 to specify radius in pixels)\n"; + ss << " circmask = " << circRad << ",\n"; + ss << "\n"; + ss << "! should a 2D gaussian background be subtracted\n"; + ss << " gausbckg = ." << (gausBckg ? "TRUE" : "FALSE") << ".,\n"; + ss << "\n"; + ss << "! how many regions should be used for adaptive histogram equalization (0 for no AHE)\n"; + ss << " nregions = " << nRegions << ",\n"; + ss << "\n"; + ss << "\n"; + ss << "!#################################################################\n"; + ss << "! Camera Calibration\n"; + ss << "!#################################################################\n"; + ss << "\n"; + ss << "! CCD pixel size on the scintillator surface [microns]\n"; + ss << " delta = " << delta << ",\n"; + ss << "\n"; + ss << "! pattern center coordinates and vendor\n"; + ss << "! vendor must be one of the following:\n"; + ss << "! EMsoft, EDAX, TSL, Oxford, Bruker\n"; + ss << "! with pctr interpreted accordingly:\n"; + ss << "! EMsoft - pcx (pixels), pcy (pixels), scintillator distance (microns)\n"; + ss << "! EDAX/TSL - x*, y*, z*\n"; + ss << "! Oxford - x*, y*, z*\n"; + ss << "! Bruker - x*, y*, z*\n"; + ss << "! note that vendors use different x*, y*, and z* : https://doi.org/10.1007/s40192-019-00137-4\n"; + ss << " pctr = " << pctr[0] << ", " << pctr[1] << ", " << pctr[2] << ",\n"; + ss << " vendor = '" << ven << "',\n"; + ss << "\n"; + ss << "! tilt angle of the camera (positive below horizontal, [degrees]\n"; + ss << " thetac = " << thetac << ",\n"; + ss << "\n"; + ss << "\n"; + ss << "!#################################################################\n"; + ss << "! Scan Information\n"; + ss << "!#################################################################\n"; + ss << "\n"; + ss << "! dimensions of scan to index and pixel size\n"; + ss << "! x, y, step for an x by y scan with square pixels of 'step' microns\n"; + ss << "! x, y, sx, sy for an x by y scan with rectangular pixels of 'sx' by 'sy' microns\n"; + ss << "! string to read dimensions from a scan file (*.ang, *.ctf, or *.h5)\n"; + ss << " scandims = " << scanDims[0] << ", " << scanDims[1] << ", " << scanSteps[0] << ", " << scanSteps[1] << ",\n";//fortran version can't be string + ss << "\n"; + ss << "! h5 path of scan data folder if scandims is an h5 file (ignored otherwise)\n"; + ss << " scanname = '',\n";//N/A for fortran version + ss << "\n"; + ss << "! region of interest for indexing\n"; + ss << "! 0 (or omitted) to index the entire scan\n"; + ss << "! x0, y0, dx, dy for a (dx, dy) rectangular starting at pixel (x0, y0)\n"; + ss << "! string for an ROI mask file\n"; + ss << " roimask = '" << roi.to_string() << "',\n";//fortran version can't be string + ss << "\n"; + ss << "!#################################################################\n"; + ss << "! Indexing Parameters\n"; + ss << "!#################################################################\n"; + ss << "\n"; + ss << "! spherical harmonic bandwidth to be used (2*bw-1 should be a product of small primes for speed)\n"; + ss << "! some reasonable values are: 53, 63, 68, 74, 88, 95, 113, 122, 123, 158, 172, 188, 203, 221, 263, 284, 313\n"; + ss << "! a nice range for parameter studies is 53, 68, 88, 113, 158, 203, 263, 338 (~a factor of 1.3 between each)\n"; + ss << "! any value is now pretty fast since the transform is zero padded to the nearest fast FFT size\n"; + ss << " bw = " << bw << ",\n"; + ss << "\n"; + ss << "! should normalized / unnormalized spherical cross correlation be used?\n"; + ss << "! normalization is more robust for (esp. for lower symmetries) but is slower\n"; + ss << " normed = ." << (normed ? "TRUE" : "FALSE") << ".,\n"; + ss << "\n"; + ss << "! should newton's method orientation refinement be used?\n"; + ss << "! normalization is more robust for (esp. for lower symmetries) but is slower\n"; + ss << " refine = ." << (refine ? "TRUE" : "FALSE") << ".,\n"; + ss << "\n"; + ss << "! number of work threads\n"; + ss << "! 0 (or omitted) to multithread with an automatic number of threads\n"; + ss << "! 1 for serial threading\n"; + ss << "! N to multithread with N threads\n"; + ss << " nthread = " << nThread<< ",\n"; + ss << "\n"; + ss << "! number of patterns to index per work itme (ignored for single threading)\n"; + ss << "! should be large enough to make the task significant compared to thread overhead\n"; + ss << "! should be small enough to enable enough work items for load balancing\n"; + ss << "! should be small enough so nthread * batchsize patterns can be held in memory\n"; + ss << "! 0 (or omitted) to estimate a reasonable value based on speed\n"; + ss << " batchsize = " << batchSize << ",\n"; + ss << "\n"; + ss << "\n"; + ss << "!#################################################################\n"; + ss << "! Output Files\n"; + ss << "!#################################################################\n"; + ss << "\n"; + ss << "! output path, empty for current working directory\n"; + ss << " opath = '" << opath << "',\n";//ignored for fortran version + ss << "\n"; + ss << "! output orientation map name relative to opath [must be hdf5 type]\n"; + ss << " datafile = '" << dataFile << "',\n"; //should be hdf + ss << "\n"; + ss << "! output orientation map name relative to opath (or omitted for no vendor output) [can be ang or ctf]\n"; + ss << " vendorfile = '" << vendorFile << "',\n"; //should be hdf + ss << "\n"; + ss << "! output ipf map with {0,0,1} reference direction (or omitted for no ipf map) [must be png]\n"; + ss << " ipfmap = '" << ipfName << "',\n"; + ss << "\n"; + ss << "! output quality map with (or omitted for no quality map) [must be png]\n"; + ss << " qualmap = '" << qualName << "'\n"; + ss << " /\n"; + return ss.str(); + } + + //@brief : set pattern file and parse as much information as possible + //@param file: file to set + //@param aux : path to h5 dataset if needed + //@note : for h5 files searches for a scan that the dataset belongs to + //@note : for up1/up2 files searches for a matching .ang + //@note : for ebsp files searches for a matching ctf + void Namelist::getPatDims(std::string file, std::string aux) { + //most pattern files have some basic info + clear(); + int w, h; + uint64_t num; + PatternFile::Bits bits; + PatternFile::GetFileDims(file, w, h, bits, num, aux); + patFile = file; + patName = aux; + patDims[0] = w;//may be -1 for *.data + patDims[1] = h;//may be -1 for *.data + } + + //@brief : search for a scan file based on the current scan file name + //@param pIq: [optional] pointer to location to store image quality map from scan file + //@param pCi: [optional] pointer to location to indexing quality map from scan file + //@return : true if additional information was parsed, false otherwise + //@note : may parse pattern center, camera tilt, scan dimensions, and pixel size + bool Namelist::findScanFile(std::vector* pIq, std::vector* pCi) { + //start by getting file extension + std::string ext = ""; + std::string name = patFile;//get a copy of the pattern file + size_t pos = name.find_last_of(".");//find the last '.' in the file + if(std::string::npos != pos) { + ext = name.substr(pos+1);//extract the file extension + std::transform(ext.begin(), ext.end(), ext.begin(), [](char c){return std::tolower(c);});//convert to lowercase + } + + //next look for vendor file + bool tryRead = false; + std::string aux = patName; + if("up1" == ext || "up2" == ext) {//edax + ven = "EDAX"; + name[pos+1] = 'a'; name[pos+2] = 'n'; name[pos+3] = 'g'; + tryRead = fileExists(name); + } else if(".ebsp" == ext) {//oxford + ven = "Oxford"; + name[pos+1] = 'c'; name[pos+2] = 't'; name[pos+3] = 'f'; name.pop_back(); + tryRead = fileExists(name); + } else if("h5" == ext || "hdf" == ext || "hdf5" == ext) {//any + ven = PatternFile::GetVendor(name); + if(ven.empty()) { + ven = "EMsoft"; + } else {//we found a vendor string, hopefully this is an orientation map file + std::string dataPath = aux .substr(0, aux .find_last_of("/\\")); + std::string ebsdPath = dataPath.substr(0, dataPath.find_last_of("/\\")); + std::string scanPath = ebsdPath.substr(0, ebsdPath.find_last_of("/\\"));//this is the folder that the scan would be in + if(dataPath != aux && !dataPath.empty()) {//folder structure was as expected (could make this a bit more strict) + aux = scanPath + '/'; + tryRead = true; + } + } + } + + //try to read vendor file if found + if(tryRead) { + scanFile = name; + scanName = aux ; + if(!readScanFile(pIq, pCi)) { + scanFile = scanName = "";//file was bad + } else { + return true; + } + } + return false; + } + + //@brief : read a scan file with a known file name (scanFile/scanName) + //@param pIq: [optional] pointer to location to store image quality map from scan file + //@param pCi: [optional] pointer to location to indexing quality map from scan file + //@return : true if additional information was parsed, false otherwise + //@note : may parse pattern center, camera tilt, scan dimensions, and pixel size + bool Namelist::readScanFile(std::vector* pIq, std::vector* pCi) { + try { + xtal::OrientationMap om(scanFile, scanName);//this will throw on errors + //if we made it to here the file had everything we were after + if(NULL != pIq) pIq->swap(om.imQual); + if(NULL != pCi) pCi->swap(om.metric); + pctr [0] = om.calib.xStar ; + pctr [1] = om.calib.yStar ; + pctr [2] = om.calib.zStar ; + thetac = om.calib.cTlt ; + scanDims [0] = om .width ; + scanDims [1] = om .height; + scanSteps[0] = om .xStep ; + scanSteps[1] = om .yStep ; + return true; + } catch (...) { + } + + //if we made it to here we were unable to parse the values of interest + pctr[0] = 0 ;//x pattern center (emsoft) + pctr[1] = 0 ;//y pattern center (emsoft) + pctr[2] = 0 ;//scintillator distance in microns (emsoft) + thetac = 0 ;//camera tilt in degrees + scanDims[0] = -1 ;//scan width in pixels + scanDims[1] = -1 ;//scan height in pixels + scanSteps[0] = 0 ;//pixel width in microns + scanSteps[1] = 0 ;//pixel height in microns + return false; + } + + //@brief: create the output data file and write header data to it + void Namelist::writeFileHeader() const { + //reconstruct namelist + std::string str = namelistFile.empty() ? to_string() : namelistFile; + std::istringstream iss(str); + nml::NameList nameList; + nameList.read(iss); + + //open the file and write top level values + //this is important to do now since we don't want a failure at the end if e.g. there aren't write privileges + H5::H5File file(opath + dataFile, H5F_ACC_TRUNC); + std::string manufact = "EMSphInx", version = emsphinx::Version; + file.createDataSet("Manufacturer", H5::StrType(0, H5T_VARIABLE), H5::DataSpace(H5S_SCALAR)).write(manufact, H5::StrType(0, H5T_VARIABLE)); + file.createDataSet("Version" , H5::StrType(0, H5T_VARIABLE), H5::DataSpace(H5S_SCALAR)).write(version , H5::StrType(0, H5T_VARIABLE)); + + //write nml file (line by line + parsed values) + std::string programName("IndexEBSD"); + nameList.writeFile(file.createGroup("NMLfiles"), programName); + nameList.writeParameters(file.createGroup("NMLparameters").createGroup(programName));//copy parsed values + + //build program, date, host, and user strings string + std::stringstream ss; + time_t tm = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + ss << std::put_time(std::localtime(&tm), "%a %b %d %Y"); + programName += " (index_ebsd.cpp)"; + std::string hostName = getComputerName(); + std::string userName = getUserName(); + + //write header data + H5::Group header = file.createGroup("EMheader"); + header.createDataSet("Date" , H5::StrType(0, H5T_VARIABLE), H5::DataSpace(H5S_SCALAR)).write(ss.str() , H5::StrType(0, H5T_VARIABLE)); + header.createDataSet("HostName" , H5::StrType(0, H5T_VARIABLE), H5::DataSpace(H5S_SCALAR)).write(hostName , H5::StrType(0, H5T_VARIABLE)); + header.createDataSet("UserName" , H5::StrType(0, H5T_VARIABLE), H5::DataSpace(H5S_SCALAR)).write(userName , H5::StrType(0, H5T_VARIABLE)); + header.createDataSet("ProgramName", H5::StrType(0, H5T_VARIABLE), H5::DataSpace(H5S_SCALAR)).write(programName, H5::StrType(0, H5T_VARIABLE)); + header.createDataSet("Version" , H5::StrType(0, H5T_VARIABLE), H5::DataSpace(H5S_SCALAR)).write(version , H5::StrType(0, H5T_VARIABLE)); + } + + }//ebsd + +}//emsphinx + + +#endif//_EBSD_NML_ diff --git a/include/modality/ebsd/pattern.hpp b/include/modality/ebsd/pattern.hpp new file mode 100644 index 0000000..bd18b9b --- /dev/null +++ b/include/modality/ebsd/pattern.hpp @@ -0,0 +1,817 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _EBSD_PATTERN_H_ +#define _EBSD_PATTERN_H_ + +#include +#include +#include +#include +#include + +#include "idx/base.hpp" + +namespace emsphinx { + + namespace ebsd { + + //@brief: abstract base class to hold patterns for indexing + class PatternFile : public ImageSource { + public: + + //@brief : get patterns held in file + //@return: number of patterns + size_t numPat() const {return num;} + + //@brief : check if the patterns need to be vertically flipped + //@return: true/false if the patterns do/don't need to be flipped + bool flipY() const {return flp;} + + //@brief : set pattern shape + //@param px: width of pattern in pixels + //@param py: height of pattern in pixels + //@param bt: bit depth of pixels + void setShape(const size_t px, const size_t py, const Bits bt); + + //@brief : set number of patterns + //@param np: width of pattern in pixels + void setNum(const size_t np) {num = np;} + + //@brief : extract the next batch of patterns into a buffer + //@param out: location to write + //@param cnt: maximum number of patterns to extract + //@return : vector of the indices of each pattern extracted (e.g. {0,2,1,3} for the first 4 patterns but out of order) + //@note : implementations should be thread safe (only for other extract calls) + virtual std::vector extract(char * const out, const size_t cnt) const = 0; + + //@brief : get pattern info from a file without reading entire thing + //@param name: file name to get info from + //@param w : location to write width (-1 if the file have width information) + //@param h : location to write height (-1 if the file have height information) + //@param bit : location to write bitdepth + //@param num : location to write number (or file size in bytes if width/height are unknown) + //@param aux : pattern file path for h5 files + //@return : true if the file can be read (based on extension), false otherwise + static bool GetFileDims(const std::string name, int& w, int& h, Bits& bit, uint64_t& num, std::string aux = ""); + + //@brief : read experimental patterns from a file + //@param name: name of pattern file to read (appropriate reader will be selected from the file name) + //@param aux : auxiliary information (for hdf5 datasets this is the path to the dataset) + //@param px : width of patterns in pixels (0 to determine automatically) + //@param py : height of patterns in pixels (0 to determine automatically) + //@return : shared pointer to a pattern file + static std::shared_ptr Read(const std::string name, const std::string aux = "", const size_t px = 0, const size_t py = 0); + + //@brief : search an h5 file for pattern datasets + //@param name: name of pattern file to check (must be hdf5 type) + //@return : paths to suitable datasets (empty if none found), these are suitable for the 'aux' argument of the Read function + static std::vector SearchH5(const std::string name); + + //@brief : get the vendor string from an hdf file + //@param name: file to get vendor string from + //@return : vendor string or "" if none were found + static std::string GetVendor(std::string name); + + //@brief : read experimental patterns from individual image files + //@param fmt: expression of pattern files to read (used with printf formatting to create files) + //@param px : width of scan (how many patterns) + //@param py : height of scan (how many patterns) + //@return : shared pointer to a pattern file + static std::shared_ptr FromImages(const std::string fmt, const size_t px, const size_t py); + + protected: + size_t num ;//number of patterns + size_t byt ;//bytes per pattern (w * h * bytes/pix) + bool flp ;//do the patterns need to be vertically flipped + mutable std::mutex mut ;//mutex for thread safe access to extraction + }; + + //@brief: abstract intermediate class for cases where all patterns are stored contiguously + class ContigousPatternFile : public PatternFile { + protected: + mutable size_t idx ;//index of next pattern to extract + mutable char const * ptr ;//pointer to next pattern to extract + + public: + //@brief : extract the next batch of patterns into a buffer + //@param ptr: location to write + //@param cnt: maximum number of patterns to extract + //@return : vector of the indices of each pattern extracted (e.g. {0,2,1,3} for the first 4 patterns but out of order) + //@note : implementations should be thread safe (only for other extract calls) + std::vector extract(char * const out, const size_t cnt) const; + + //@brief: virtual destructor (this is an abstract intermediate class) + virtual ~ContigousPatternFile() = 0; + }; + + //@bierf: intermediate class for cases where all patterns are NOT stored contiguously (e.g. individual image file per patterb) + class StreamedPatternFile : public PatternFile { + protected: + //all streamed pattern files need an input source + mutable size_t idx;//index of next pattern to extract + std::istream& is ; + + public: + //@brief : extract the next batch of patterns into a buffer + //@param ptr: location to write + //@param cnt: maximum number of patterns to extract + //@return : vector of the indices of each pattern extracted (e.g. {0,2,1,3} for the first 4 patterns but out of order) + //@note : implementations should be thread safe (only for other extract calls) + std::vector extract(char * const out, const size_t cnt) const; + + //@brief : construct a streamed pattern file + //@param s: input stream to pull patterns from + StreamedPatternFile(std::istream& s) : is(s) {} + + //@brief: virtual destructor (this is an abstract intermediate class) + virtual ~StreamedPatternFile() = 0; + }; + + //@bierf: abstract intermediate class for cases where all patterns are NOT stored contiguously (e.g. individual image file per patter or chunked hdf5) + class ChunkedPatternFile : public PatternFile { + public: + //@brief: virtual destructor (this is an abstract intermediate class) + virtual ~ChunkedPatternFile() = 0; + }; + + }//ebsd + +}//emsphinx + +//////////////////////////////////////////////////////////////////////// +// Non-abstract Classes // +//////////////////////////////////////////////////////////////////////// + +#include + +#include "util/sysnames.hpp"//fileSize +#include "util/bmp.hpp" +#include "H5Cpp.h" + +namespace emsphinx { + + namespace ebsd { + + //@brief: class to hold all patterns in memory + class BufferedPatternFile : public ContigousPatternFile { + // std::vector buff;//pattern buffer + + public: + std::vector buff;//pattern buffer + //@brief: allocate buffer using current pattern size + number + //@note : buffer is unitialized + void allocate() {buff = std::vector(imBytes() * num); ptr = data();} + + //@param : get pointer to underlying buffer + //@return: pointer to buffer + char* data() {return buff.data();} + }; + + //@brief: class to read patterns from an ifstream (this actually performs as well as a memory map on the linux systems tested) + class IfStreamedPatternFile : public StreamedPatternFile { + std::ifstream ifs ;//underlying file + const size_t fByt;//size of underlying file in bytes + //if this class is modified to include pre/post padding bytes it will be significantly more flexible (and can accommodate the oxford format) + + public: + //@brief : open a memory mapped pattern file + //@param name: name of file to map + //@note : all other members are uninitalized + IfStreamedPatternFile(std::string name) : ifs(name, std::ios::in | std::ios::binary), fByt(fileSize(name)), StreamedPatternFile(ifs) {} + + //@brief : construct the pointer to the data start from an offset in bytes + //@param of: offset to data start in bytes + //@note : sets number of patterns using file size and pattern size + void setOffset(const size_t of); + }; + + //@brief: oxford has enough peculiarities that I've written a separate class for now (it may be worth generalizing the streamed pattern file interface to make this unnecessary) + class OxfordPatternFile : public PatternFile { + mutable std::ifstream ifs;//underlying file + std::vector idx;//order of patterns in file (usually close to 0,1,2,3,... but not always) + mutable std::vector::const_iterator nxt;//next pattern to extract + + public: + //@brief : open an oxford pattern file and get order + //@param name: name of file to map + OxfordPatternFile(std::string name); + + //@brief : extract the next batch of patterns into a buffer + //@param out: location to write + //@param cnt: maximum number of patterns to extract + //@return : vector of the indices of each pattern extracted (e.g. {0,2,1,3} for the first 4 patterns but out of order) + //@note : implementations should be thread safe (only for other extract calls) + std::vector extract(char * const out, const size_t cnt) const {return extract(out, cnt, NULL, NULL);} + + //@brief : extract the next batch of patterns into a buffer + //@param out: location to write + //@param cnt: maximum number of patterns to extract + //@param vx : location to put x coordinates (or NULL to ignore coordinates) + //@param vy : location to put y coordinates (or NULL to ignore coordinates) + //@return : vector of the indices of each pattern extracted (e.g. {0,2,1,3} for the first 4 patterns but out of order) + //@note : implementations should be thread safe (only for other extract calls) + std::vector extract(char * const out, const size_t cnt, std::vector* vx, std::vector* vy) const; + }; + + }//ebsd + +}//emsphinx + +//////////////////////////////////////////////////////////////////////// +// Implementations // +//////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +namespace emsphinx { + + namespace ebsd { + + namespace detail { + //@brief : get the extension of a file name + //@param name: file name to get extension of + //@return : extension (all lower case) + std::string getFileExt(const std::string name) { + size_t pos = name.find_last_of(".");//find the last '.' in the name + if(std::string::npos == pos) return "";//handle files with no extension + std::string ext = name.substr(pos+1);//extract the file extension + std::transform(ext.begin(), ext.end(), ext.begin(), [](char c){return std::tolower(c);});//convert to lowercase + return ext; + } + } + + //@brief : set pattern shape + //@param px: width of pattern in pixels + //@param py: height of pattern in pixels + //@param bt: bit depth of pixels + void PatternFile::setShape(const size_t px, const size_t py, const Bits bt) { + bits = bt;//pixel type + w = px;//width of patterns + h = py;//height of patterns + switch(bt) { + case Bits::U8 : byt = 1; break; + case Bits::U16: byt = 2; break; + case Bits::F32: byt = 4; break; + case Bits::UNK: + default : throw std::runtime_error("unvalid bit type"); + } + byt *= w * h; + } + + //@brief: virtual constructor (this is an abstract intermediate class) + ContigousPatternFile::~ContigousPatternFile() {} + + //@brief: virtual constructor (this is an abstract intermediate class) + StreamedPatternFile::~StreamedPatternFile() {} + + //@brief: virtual constructor (this is an abstract intermediate class) + ChunkedPatternFile::~ChunkedPatternFile() {} + + //@brief : get pattern info from a file without reading entire thing + //@param name: file name to get info from + //@param w : location to write width (-1 if the file have width information) + //@param h : location to write height (-1 if the file have height information) + //@param bit : location to write bitdepth + //@param num : location to write number (or file size in bytes if width/height are unknown) + //@param aux : pattern file path for h5 files + //@return : true if the file can be read (based on extension), false otherwise + bool PatternFile::GetFileDims(const std::string name, int& w, int& h, Bits& bit, uint64_t& num, std::string aux) { + w = h = -1; + num = 0; + bit = Bits::UNK; + const std::string ext = detail::getFileExt(name); + const uint64_t fileBytes = fileSize(name); + + //handle easy types first (these have a header with all required data) + if("up1" == ext || "up2" == ext || "ebsp" == ext) { + std::shared_ptr pat = Read(name); + w = (int)pat->width (); + h = (int)pat->height (); + num = pat->numPat (); + bit = pat->pixelType(); + } else if("data" == ext) { + bit = Bits::F32; + num = fileBytes; + } else if("h5" == ext || "hdf" == ext || "hdf5" == ext) { + //open the dataset and get information + hsize_t dims[3]; + H5::DataSet dSet = H5::H5File(name, H5F_ACC_RDONLY).openDataSet(aux);//open the file and get the dataset we're after + if(3 != dSet.getSpace().getSimpleExtentNdims()) throw std::runtime_error("hdf pattern dataset must be 3D"); + dSet.getSpace().getSimpleExtentDims(dims);//read extent in each dimension + w = (int)dims[2]; + h = (int)dims[1]; + num = dims[0]; + + //determine data type + H5::DataType type = dSet.getDataType(); + if (type == H5::DataType(H5::PredType::NATIVE_UINT8 )) bit = ImageSource::Bits::U8 ; + else if(type == H5::DataType(H5::PredType::NATIVE_UCHAR )) bit = ImageSource::Bits::U8 ; + else if(type == H5::DataType(H5::PredType::NATIVE_UINT16)) bit = ImageSource::Bits::U16; + else if(type == H5::DataType(H5::PredType::NATIVE_FLOAT )) bit = ImageSource::Bits::F32; + if(ImageSource::Bits::UNK == bit) throw std::runtime_error("only uint8, uint16, and float hdf patterns are supported"); + } else { + return false; + } + return true; + } + + //@brief : read experimental patterns from a file + //@param name: name of pattern file to read (appropriate reader will be selected from the file name) + //@param aux : auxiliary information (for hdf5 datasets this is the path to the dataset) + //@param px : width of patterns in pixels (0 to determine automatically) + //@param py : height of patterns in pixels (0 to determine automatically) + //@return : shared pointer to a pattern file + std::shared_ptr PatternFile::Read(const std::string name, const std::string aux, const size_t px, const size_t py) { + //extract extension + const std::string ext = detail::getFileExt(name); + const uint64_t fileBytes = fileSize(name); + + //read based on file extension + if(0 == ext.compare("up1") || 0 == ext.compare("up2")) { + //open file and read header + int32_t header[4]; + { + //read header + std::ifstream is(name, std::ios::in | std::ios::binary); + is.read((char*)header, 4 * sizeof(int32_t)); + } + + //parse header (logic here courtesy of stuart) + int32_t& vers = header[0];//up* file version + int32_t& width = header[1];//pattern width in pixels + int32_t& height = header[2];//pattern width in pixels + int32_t& dStart = header[3];//data start position + if(width < 1 || height < 1 || width > 5000 || height > 5000 || dStart < 0) { + if(vers > 2) { + height = width; + width = vers; + dStart = 8; + } + if(width < 1 || height < 1 || width > 5000 || height > 5000 || dStart < 0) throw std::runtime_error("invalid UP file"); + } + if((0 != px && px != width) || (0 != py && py != height)) throw std::runtime_error("patterns aren't expected shape"); + const Bits b = 0 == ext.compare("up1") ? ImageSource::Bits::U8 : ImageSource::Bits::U16; + + //construct with either ifstream + std::shared_ptr ptr = std::make_shared(name);//memory map entire file + ptr->setShape((size_t)width, (size_t)height, b); + ptr->setOffset(dStart); + ptr->flp = true;//EDAX files need to be flipped + return std::shared_ptr(ptr); + } else if(0 == ext.compare("h5") || 0 == ext.compare("hdf") || 0 == ext.compare("hdf5")) { + //open the dataset and get information + H5::H5File file = H5::H5File(name, H5F_ACC_RDONLY);//open the file + H5::DataSet dSet = file.openDataSet(aux);//get the dataset we're after + hsize_t dims[3]; + if(3 != dSet.getSpace().getSimpleExtentNdims()) throw std::runtime_error("hdf pattern dataset must be 3D"); + dSet.getSpace().getSimpleExtentDims(dims);//read extent in each dimension + if((0 != px && px != dims[2]) || (0 != py && py != dims[1])) throw std::runtime_error("patterns aren't expected shape"); + + //determine if patterns need to be flipped using vendor + bool vendorFlip = false; + std::string vendor = GetVendor(name); + if ("EDAX" == vendor) vendorFlip = true ; + else if("Oxford" == vendor) vendorFlip = false; + else if("Bruker" == vendor) vendorFlip = false; + else if("DREAM.3D" == vendor) vendorFlip = false; + else if("EMsoft" == vendor) vendorFlip = true; + else throw std::runtime_error("unknown EBSD vendor: " + vendor); + + //determine data type + Bits bits = ImageSource::Bits::UNK; + H5::DataType type = dSet.getDataType(); + if (type == H5::DataType(H5::PredType::NATIVE_UINT8 )) bits = ImageSource::Bits::U8 ; + else if(type == H5::DataType(H5::PredType::NATIVE_UCHAR )) bits = ImageSource::Bits::U8 ; + else if(type == H5::DataType(H5::PredType::NATIVE_UINT16)) bits = ImageSource::Bits::U16; + else if(type == H5::DataType(H5::PredType::NATIVE_FLOAT )) bits = ImageSource::Bits::F32; + if(ImageSource::Bits::UNK == bits) throw std::runtime_error("only uint8, uint16, and float hdf patterns are supported"); + + //get offset from file start + haddr_t offset; + try { + offset = dSet.getOffset();//get the offset to the dataset start + } catch(H5::DataSetIException&) { + offset = (haddr_t)-1;//HAADR_UNDEF if not contigous (<0) + } + + //try to raw access the dataset + H5::DSetCreatPropList props = dSet.getCreatePlist(); + if(0 == props.getExternalCount() && //we can't use a memory map if the dataset references external files + ((H5D_COMPACT == props.getLayout() || H5D_CONTIGUOUS == props.getLayout())) && //we can't memory map datasets unless they are contigous in memory + 0 != props.getNfilters() && //we can't memory map compressed datasets + offset != (haddr_t)-1 ) {//negative value (HADDR_UNDEF) means that hdf5 couldn't compute the data offset + //we can try memory mapping the file + try { + std::shared_ptr ptr = std::make_shared(name);//memory map entire file + ptr->setShape(dims[2], dims[1], bits); + ptr->setOffset(dSet.getOffset()); + ptr->setNum(dims[0]); + ptr->flp = vendorFlip; + return std::shared_ptr(ptr); + } catch (...) { + //fall back to normal hdf5 reading if we failed + } + } + + //if we made it this far we either can't get raw access or failed to do so + //just read entire dataset into memory (it may be good to add a ChunkedPatternFile derived option for large files) + std::shared_ptr ptr = std::make_shared(); + ptr->setShape(dims[2], dims[1], bits); + ptr->setNum(dims[0]); + ptr->allocate(); + dSet.read((void*)ptr->data(), H5::PredType::NATIVE_UINT8); + ptr->flp = vendorFlip; + return std::shared_ptr(ptr); + } else if(0 == ext.compare("data")) { + //raw 32bit float patterns with no header, construct with ifstream + std::shared_ptr ptr = std::make_shared(name);//memory map entire file + ptr->setShape(px, py, Bits::F32); + ptr->setOffset(0); + ptr->flp = false; + return std::shared_ptr(ptr); + } else if(0 == ext.compare("ebsp")) {//oxford raw pattern format + //the format for this reader was parsed from the dream3d reader in OxfordReader.cpp + // const std::string ext2 = detail::getFileExt(aux); + // if(0 != ext2.compare("dat")) throw std::runtime_error(name + " doesn't have associated `dat' file"); + //there is a binary ctf type file in the associated .dat file + //for each pixel in the orientation map there are 25 bytes in a planar format: + // -uint8_t : phase + // -3x float: euler angles + // -float : MAD + // -uint8_t : BC + // -uint8_t : BS + // -uint8_t : bands + // -uint8_t : unknown + // -float : unknown + //with first numPat bytes with the phase of each pixel, then 12*numPat bytes with eulers, etc + std::shared_ptr ptr = std::make_shared(name); + return ptr; + } + throw std::runtime_error("couldn't find reader for '" + name + "'"); + } + + std::vector PatternFile::SearchH5(const std::string name) { + H5::Exception::dontPrint();//silence caught errors + + //@brief: function to loop over an hdf5 location adding children names to a list + //@param gid : location id + //@param name : name of child + //@param opData: operator data (pointer to vector to store names in) + H5G_iterate_t iterFunc = [](hid_t gid, const char * name, void *opData)->herr_t{ + std::vector* pDsets = (std::vector*)opData; + pDsets->push_back(name); + return 0; + }; + + //open file and allocate space for valid names + std::vector choices; + H5::H5File file(name.c_str(), H5F_ACC_RDONLY); + + //recursively loop over locations in files checking for validity + std::stack locs; + locs.push(".");//add root + while(!locs.empty()) { + //get top of stack + std::string loc = locs.top(); + locs.pop(); + + //get all children of top + int idx = 0; + std::vector children; + file.iterateElems(loc.c_str(), &idx, iterFunc, &children); + + //loop over children classifying + for(std::string& child : children) { + std::string name = loc + "/" + child;//build full path + try { + file.openGroup(name.c_str());//try to open as a group + locs.push(name);//if openGroup didn't throw it is a group, add to recursion + } catch(...) {//this location is a dataset, check if it is valid + H5::DataSpace space = file.openDataSet(name.c_str()).getSpace(); + if(3 == space.getSimpleExtentNdims()) {//pattern datasets must be 3d + hsize_t dims[3]; + space.getSimpleExtentDims(dims); + if(dims[2] > 4) choices.push_back(name);//ebsd H5 files can have RGB(A) images for e.g. coordinate system + } + } + } + } + return choices; + } + + //@brief : get the vendor string from an hdf file + //@param name: file to get vendor string from + //@return : vendor string or "" if none were found + std::string PatternFile::GetVendor(std::string name) { + //find the manufacturer dataset + H5::H5File file = H5::H5File(name, H5F_ACC_RDONLY);//open the file + int idx = 0; + int pad = -1;//1 for "Manufacturer", 2 for " Manufacturer", 0 for nothing + file.iterateElems(".", &idx, [](int, const char* nm, void* pPad)->int{ + if(std::string("Manufacturer") == nm) { + *(int*)pPad = 0;//save type + return 1;//non zero -> stop searching + } else if(std::string(" Manufacturer") == nm) {//some EDAX files have a stray leading space... + *(int*)pPad = 1;//save type + return 1;//non zero -> stop searching + } + return 0;//0 -> keep searching + }, &pad); + if(-1 == pad) throw std::runtime_error(name + " doesn't have a Manufacturer string"); + std::string manStr = pad == 1 ? " Manufacturer" : "Manufacturer"; + + //determine the vendor and use to set pattern flip flag + try { + std::string vendor; + file.openDataSet(manStr).read( vendor, H5::StrType(0, H5T_VARIABLE), H5::DataSpace(H5S_SCALAR)); + return vendor; + } catch (H5::Exception&) {//H5T_VARIABLE + std::string seems to fail here... + std::string vendor(128, 0); + file.openDataSet(manStr).read((void*)vendor.data(), H5::StrType(0, vendor.size()), H5::DataSpace(H5S_SCALAR));//try reading as fixed length + vendor.erase(std::find(vendor.begin(), vendor.end(), '\0'), vendor.end());//get rid of extra padding nulls + return vendor; + } + } + + //@brief : read experimental patterns from individual image files + //@param fmt: expression of pattern files to read (used with printf formatting to create files) + //@param px : width of scan (how many patterns) + //@param py : height of scan (how many patterns) + //@return : shared pointer to a pattern file + std::shared_ptr PatternFile::FromImages(const std::string fmt, const size_t px, const size_t py) { + //generate a list of filenames + std::vector fileNames; + std::string name;//location to write filename + for(size_t j = 0; j < py; j++) { + for(size_t i = 0; i < px; i++) { + const size_t bytes = std::snprintf(NULL, 0, fmt.data(), i, j) + 1;//how much space will it take to create the string (+ null character) + if(bytes > name.size()) name.resize(bytes); + std::snprintf(&name[0], name.size(), fmt.data(), i, j); + fileNames.push_back(name); + } + } + if(fileNames.empty()) throw std::runtime_error("no file names created for image files"); + + //read based on file extension + const std::string ext = detail::getFileExt(fmt);//extract extension + if(0 == ext.compare("bmp")) { + //read first pattern header + bmp::Header hdr; + { + std::ifstream is(fileNames.front(), std::ios::in | std::ios::binary); + hdr.read(is); + } + + //parse shape etc + std::shared_ptr ptr = std::make_shared(); + const size_t bitCount = hdr.bitCount; + switch(bitCount) { + case 8 : break; + case 24: break;//potentially a grayscale image as rgb + default: throw std::runtime_error("unsupported bitmap pattern bit depth"); + } + ptr->setShape(hdr.width, hdr.height, ImageSource::Bits::U8); + ptr->setNum(fileNames.size()); + + //allocate data and get pointer + ptr->allocate(); + const size_t imBytes = ptr->imBytes(); + char* buff = ptr->data(); + + //loop over images reading + for(const std::string nm : fileNames) { + //open file and read header + std::ifstream is(nm, std::ios::in | std::ios::binary); + hdr.read(is); + + //make sure the pattern size is the same + if(hdr.width != ptr->width() || hdr.height != ptr->height()) throw std::runtime_error("pattern shape mismatch"); + if(hdr.bitCount != bitCount) throw std::runtime_error("pattern bitdepth mismatch"); + + //read the pattern + hdr.readImage(is, buff, true); + buff += imBytes; + } + + std::ofstream os("test.raw", std::ios::out | std::ios::binary); + os.write(ptr->data(), ptr->imBytes() * fileNames.size()); + + //return upcasted pointer + return std::shared_ptr(ptr); + } + + throw std::runtime_error("couldn't find reader for '" + name + "' (currently only bmp images are supported)"); + } + + //@brief : extract the next batch of patterns into a buffer + //@param ptr: location to write + //@param cnt: maximum number of patterns to extract + //@return : index of first and last pattern extract (exlusive so second - first patterns were extracted) + //@note : implementations should be thread safe (only for other extract calls) + std::vector ContigousPatternFile::extract(char * const out, const size_t cnt) const { + std::lock_guard lock(mut);//only 1 thread can get patterns at once + const size_t numExt = std::min(numPat() - idx, cnt);//determine how many patterns we can extract + char const * const ptrNew = ptr + numExt * imBytes();//get end of range to copy + std::copy(ptr, ptrNew, out);//copy patterns to output + std::vector res(numExt);//build vector to hold extraction index list + std::iota(res.begin(), res.end(), idx);//fill index list + idx += numExt;//update index of next patterns + ptr = ptrNew;//update pointer to next pattern + if(idx == numPat()) ptr = 0;//we've run out of patterns + return res; + } + + //@brief : extract the next batch of patterns into a buffer + //@param ptr: location to write + //@param cnt: maximum number of patterns to extract + //@return : vector of the indices of each pattern extracted (e.g. {0,2,1,3} for the first 4 patterns but out of order) + //@note : implementations should be thread safe (only for other extract calls) + std::vector StreamedPatternFile::extract(char * const out, const size_t cnt) const { + std::lock_guard lock(mut);//only 1 thread can get patterns at once + const size_t numExt = std::min(numPat() - idx, cnt);//determine how many patterns we can extract + const size_t readBytes = numExt * imBytes();//get end of range to copy + is.read(out, readBytes);//read from istream into the output + std::vector res(numExt);//build vector to hold extraction index list + std::iota(res.begin(), res.end(), idx);//fill index list + idx += numExt;//update index of next patterns + return res; + } + + //@brief : construct the pointer to the data start from an offset in bytes + //@param of: offset to data start in bytes + //@note : sets number of patterns using file size and pattern size + void IfStreamedPatternFile::setOffset(const size_t of) { + num = (fByt - of) / imBytes(); + ifs.seekg(of); + idx = 0; + } + + //@brief : open an oxford pattern file and get order + //@param name: name of file to map + OxfordPatternFile::OxfordPatternFile(std::string name) : ifs(name, std::ios::in | std::ios::binary) {//open the input file + //the oxford format starts an 8 bytes header followed by a sequence of uint64_t's with the offset to the data for each pattern + //the first pattern is always immediately after the offsets + //start by reading the header and first offset to determine the pattern count + uint64_t offset; + uint8_t header[8]; + ifs.read((char*)header, 8);//read header bytes + if(//0xFF != header[0] || sometimes this is 0xFE... + 0xFF != header[1] || + 0xFF != header[2] || + 0xFF != header[3] || + 0xFF != header[4] || + 0xFF != header[5] || + 0xFF != header[6] || + 0xFF != header[7]) throw std::runtime_error("unexpected header for " + name);//check header + if(!ifs.read((char*)&offset, sizeof(offset))) throw std::runtime_error("failed to read first pattern position from " + name);//read offset to first pattern + const uint64_t numPat = (offset - 8) / 8;//compute pattern count from offset to first pattern + + //now read all the offsets at once + ifs.seekg(8);//go back to first index + std::vector offsets(numPat);//save space to hold all offsets + if(!ifs.read((char*)offsets.data(), sizeof(uint64_t) * offsets.size())) throw std::runtime_error("failed to read offsets from " + name); + + //next seek to the first pattern and get some info + //each pattern block has a 16 byte header, some (maybe 0) padding space, the pattern, and then 18 tail bytes: + // uint8_t x position + // double x position (in microns) + // uint8_t y position + // double y position (in microns) + ifs.seekg(offsets[0]); + uint32_t leadIn, width, height, bytes; + if(!ifs.read((char*)&leadIn, sizeof(uint32_t))) throw std::runtime_error("failed to read lead in of first pattern in " + name); + if(!ifs.read((char*)&height, sizeof(uint32_t))) throw std::runtime_error("failed to read width of first pattern in " + name); + if(!ifs.read((char*)&width , sizeof(uint32_t))) throw std::runtime_error("failed to read height of first pattern in " + name); + if(!ifs.read((char*)&bytes , sizeof(uint32_t))) throw std::runtime_error("failed to read bytes of first pattern in " + name); + + //parse the bit depth + Bits bits; + if(bytes == width * height) { + bits = Bits::U8; + } else if(bytes == width * height * 2) { + bits = Bits::U16; + } else { + throw std::runtime_error("couldn't determine pixel type for patterns in " + name); + } + + //next compute the size of a the first pattern + uint64_t blockBytes = 16 + //4 * uint32_t for leadIn, width, height, + bytes + leadIn + //padding bytes between the header + data + bytes + //actual image data + 18 ; //tail data + + //now convert from offsets to index of each pattern in order + idx.assign(numPat, numPat);//save space to hold all offsets and fill with numPat (largest index + 1) + for(size_t i = 0; i < numPat; i++) {//loop over offsets + uint64_t off = offsets[i] - offset;//convert from absolute offset to offset to from first pattern + if(0 != off % blockBytes) throw std::runtime_error("inconsistent block sizes aren't currently supported for .ebsp files"); + off /= blockBytes;//we know of the offset of pattern i (in patterns) + idx[off] = i;//what we actually need to know is which pattern is at each position + } + + //make sure we got all the patterns + for(const size_t& i : idx) { + if(i == numPat) throw std::runtime_error("not all patterns were found in " + name); + } + + //if we made it this far we can stream the file, save all the information required + setShape(width, height, bits); + setNum(numPat); + flp = false; + + //move to first pattern + nxt = idx.cbegin(); + ifs.seekg(offset); + } + + //@brief : extract the next batch of patterns into a buffer + //@param out: location to write + //@param cnt: maximum number of patterns to extract + //@param vx : location to put x coordinates (or NULL to ignore coordinates) + //@param vy : location to put y coordinates (or NULL to ignore coordinates) + //@return : vector of the indices of each pattern extracted (e.g. {0,2,1,3} for the first 4 patterns but out of order) + //@note : implementations should be thread safe (only for other extract calls) + std::vector OxfordPatternFile::extract(char * const out, const size_t cnt, std::vector* vx, std::vector* vy) const { + if(nxt == idx.cend()) return std::vector();//handle trivial case (nothing available) + + //extract as many patterns as possible (up to cnt) + std::lock_guard lock(mut);//only 1 thread can get patterns at once + std::vector ret; + uint32_t leadIn, width, height, bytes; + const size_t pByt = imBytes(); + for(size_t i = 0; i < cnt; i++) {//loop over requested values + //read block header + ifs.read((char*)&leadIn, sizeof(uint32_t)); + ifs.read((char*)&height, sizeof(uint32_t)); + ifs.read((char*)&width , sizeof(uint32_t)); + ifs.read((char*)&bytes , sizeof(uint32_t)); + + //make sure the pattern is the same shape as the first one + if(width != this->width() || height != this->height() || bytes != pByt) { + std::stringstream ss; + ss << "pattern " << std::distance(idx.cbegin(), nxt) << " isn't the same shape as the first pattern"; + throw std::runtime_error(ss.str()); + } + + //skip lead in and read + ifs.ignore(leadIn);//skip padding space + ifs.read(out + i * pByt, pByt);//read the pattern + // ifs.ignore(18);//skip tail data + + double x, y; + uint8_t ix, iy; + ifs.read((char*)&ix, sizeof(uint8_t)); + ifs.read((char*)& x, sizeof(double )); + ifs.read((char*)&iy, sizeof(uint8_t)); + ifs.read((char*)& y, sizeof(double )); + if(NULL != vx) vx->push_back(x); + if(NULL != vy) vy->push_back(y); + + //save index of pattern and advance + ret.push_back(*nxt); + ++nxt; + } + return ret; + } + + }//ebsd + +}//emsphinx + +#endif//_EBSD_PATTERN_H_ diff --git a/include/sht/sht_xcorr.hpp b/include/sht/sht_xcorr.hpp new file mode 100644 index 0000000..73e5b3a --- /dev/null +++ b/include/sht/sht_xcorr.hpp @@ -0,0 +1,1370 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _SHT_XCORR_H_ +#define _SHT_XCORR_H_ + +#include +#include +#include + +#include "util/fft.hpp" + +namespace emsphinx { + + namespace sphere { + //@brief: helper struct to compute the cross correlation of 2 spherical functions from their spherical harmonic transformations + //@reference: Gutman, B., Wang, Y., Chan, T., Thompson, P. M., & Toga, A. W. (2008, October). Shape registration with spherical cross correlation. In 2nd MICCAI workshop on mathematical foundations of computational anatomy (pp. 56-67). + //@note: the reference is for the generic case (complex functions) but restricting to real valued functions allow for savings from symmetry: + // \hat{f}(l, -m) = \hat{f}(l, m) * (-1)^m + // additionally since the decomposition of the wigner D function to 2 wigner d functions @ pi/2 introduces more symmetry + // d^j_{-k,-m} = (-1)^( k- m) d^j_{k,m} + // d^j_{ k,-m} = (-1)^(j+ k+2m) d^j_{k,m} + // d^j_{-k, m} = (-1)^(j+2k+3m) d^j_{k,m} + // d^j_{ m, k} = (-1)^( k- m) d^j_{k,m} + // finally since the cross correlation of the real functions is also real there is another factor of 2 savings + //@note: variable names are consistent with Gutman et. al. (see eq 12 for details) except 'j' is used in place of 'l' + template + struct Correlator { + //@brief : construct a spherical correlator for a given bandwidth + //@param bandWidth: maximum bandwidth of spherical harmonic to use in correlation (exclusive) + Correlator(const size_t bandWidth); + + //@brief : compute the rotation of the maximum cross correlation between two spherical functions + //@param flm: spherical harmonic coefficients for the first function + //@param gln: spherical harmonic coefficients for the second function + //@param fMr: true/false if there is/isn't a mirror plane in the first fuction + //@param fNf: rotational symmetry about z axis in first function (1 for no rotational symmetry) + //@param eu : location to write rotation of maximum cross correlation as ZYZ euler angle + //@param ref: true/false to use/not use real space refinement + //@param eps: convergence criterion for refinement (step size in fractional pixels where, absolute precision is ~eps * pi / bw radians) + //@return : maximum cross correlation + Real correlate(std::complex const * const flm, std::complex const * const gln, const bool fMr, const size_t fNf, Real * const eu, const bool ref = true, const Real eps = Real(0.01)); + + //@brief : compute a subpixel maxima in the cross correlation using interpolation + //@param ind0: pixel to search from (should be near a local maxima) + //@param eu : location to write rotation of maximum cross correlation as ZYZ euler angle + //@return : estimated cross correlation of peak + Real interpPeak(size_t ind0, Real * const eu); + + //@brief : compute a subpixel maxima in the cross correlation by either interpolation or refinement + //@param flm : spherical harmonic coefficients for the first function + //@param gln : spherical harmonic coefficients for the second function + //@param fMr : true/false if there is/isn't a mirror plane in the first fuction + //@param fNf : rotational symmetry about z axis in first function (1 for no rotational symmetry) + //@param eu : location to write rotation of maximum cross correlation as ZYZ euler angle + //@param eps : convergence criterion for refinement (step size in fractional pixels where, absolute precision is ~eps * pi / bw radians) + //@return : cross correlation of peak + Real refinePeak(std::complex const * const flm, std::complex const * const gln, const bool fMr, const size_t fNf, Real * const eu, const Real eps = Real(0.01)); + + //@brief : extract an (N+1)^3 neighborhood around a pixel using periodic boundary conditions + //@template N: half window size of neighborhood to extract + //@param idx : vectorized index to extract neighborhood around + //@param nh : 3D array of size (N+1)^3 to write neighborhood into + template void extractNeighborhood(const size_t idx, Real (&nh)[N*2+1][N*2+1][N*2+1]) const; + + //@brief : compute the index of the pixel closest to an orientation + //@param eu: ZYZ euler angle to find nearest pixel of + //@return : vectorized pixel index + size_t eulerIndex(Real const * const eu) const; + + //@brief : compute the orientation of an index + //@param idx: vectorized pixel index + //@param eu : location to write ZYZ euler angles + void indexEuler(const size_t idx, Real * const eu) const; + + //@brief : get the cross correlation from the previous call to correlate + //@return: cross correlation grid + const fft::vector& getXC() const {return xc;} + + //@brief : copy the cross correlation from the previous call and convert to ZXZ euler angles with origin at 0 + //@param zxz: zxz euler angle grid to write cross correlation to + //@note : phi1 (rotation about Z) increments fastest, phi2 (rotation about Z'') middle, and Phi (rotation about X') slowest + void extractBunge(Real * const zxz) const; + + //@brief : get the maximum bandwidth + //@return: maximum bandwidth + size_t getBw() const {return bw;} + + //@brief : get the cross correlation grid size + //@return: side length of correlation cube (at least 2 * bw - 1, may be zero padded larger) + size_t getCubeSize() const {return slP;} + + //@brief : get half the cross correlation grid size + //@return: side length of correlation cube (at least 2 * bw - 1, may be zero padded larger) + size_t getHalfSize() const {return bwP;} + + //@brief : compute the first and second derivatives of the cross correlation at a single rotation + //@param flm: spherical harmonic coefficients for the first function + //@param gln: spherical harmonic coefficients for the second function + //@param eu : rotation to compute derivatives of cross correlation for as ZYZ euler angle + //@param jac: location to write jacobian of cross correlation {d/(d eu[0]), d/(d eu[1]), d/(d eu[2])} + //@param hes: location to write hessian (3x3 matrix as 9 component vector) of cross correlation hes_ij = d/(d eu[i]) * d/(d eu[j]) + //@param mBW: maximum bandwidth to use in calculation (must be <= bw) + //@param fMr: true/false if there is/isn't a mirror plane in the first fuction + //@param fNf: rotational symmetry about z axis in first function (1 for no rotational symmetry) + //@param der: true/false to compute derivatives/only cross correlation + //@return : cross correlation for rotation eu + Real derivatives(std::complex const * const flm, std::complex const * const gln, Real const * const eu, Real * const jac, Real * const hes, const size_t mBW, const bool fMr, const size_t fNf, const bool der = true); + + protected: + + //@brief : compute the cross correlation between two spherical functions + //@param flm: spherical harmonic coefficients for the first function + //@param gln: spherical harmonic coefficients for the second function + //@param fMr: true/false if there is/isn't a mirror plane in the first fuction + //@param fNf: rotational symmetry about z axis in first function (1 for no rotational symmetry) + //@param pXc: location to write cross correlation + void compute(std::complex const * const flm, std::complex const * const gln, const bool fMr, const size_t fNf, Real * const pXc); + + //@brief : find the maximum cross correlation grid point + //@return: index of maximum cross correlation from previous call to correlate + size_t findPeak(); + + struct Constants;//helper struct to hold read only constants + const size_t bw ;//maximum bandwidth to use (exclusive) + const size_t sl ;//side length of grid in euler space (2 * bandWidth - 1) + const size_t slP ;//zero padded side length of grid in euler space (2 * bandWidth - 1) + const size_t bwP ;//zero padded bandwidth (slP + 1) / 2 + const std::shared_ptr xcLut;//read only values (can be shared across threads) + std::vector< std::complex > fm ;//2d lookup table to hold \hat{f}(j,m) * d^j_{m, k} for all j and m (for any given k) + std::vector< std::complex > gn ;//1d lookup table to hold \bar{\hat{g}(j,n)} * d^j_{k,n} for all j (for any given k and n) + fft::vector< std::complex > fxc ;//fft of cross correlation (in fftw multi dimensional real format) + fft::vector< Real > xc ;//real space cross correlation (this is what we're after) + std::vector< Real > dBeta;//wigner (lowercase) d lookup table for arbitrary beta (for realspace refinement) + }; + + //@brief: intermediate class to impose condition that makes abstracting (un)normalized easier + template + struct PhaseCorrelator : protected Correlator { + //@brief : require bandwidth for construction + //@param bw: bandwidth + PhaseCorrelator(const size_t bw) : Correlator(bw) {} + + //@brief: default destructor (for unique_ptr) + virtual ~PhaseCorrelator() = default; + + //@brief : get a copy of the stored spherical correlator + //@return: unique pointer to copy of current correlator + virtual std::unique_ptr clone() const = 0; + + //@brief : compute the rotation of the maximum (un)normalized cross correlation between two spherical functions + //@param gln: spherical harmonic coefficients for the template function + //@param eu : location to write rotation of maximum normalized cross correlation as ZYZ euler angle + //@param ref: true/false to use/not use real space refinement + //@param eps: convergence criterion for refinement (step size in fractional pixels where, absolute precision is ~eps * pi / bw radians) + //@return : maximum (un)normalized cross correlation + //@note : flm, fMr, and fNf stored in derived class + virtual Real correlate(std::complex const * const gln, Real * const eu, const bool ref = true, const Real eps = Real(0.01)) = 0; + + //@brief : compute a subpixel maxima in the (un)normalized cross correlation by either interpolation or refinement + //@param gln : spherical harmonic coefficients for the second function + //@param eu : location to write rotation of maximum cross correlation as ZYZ euler angle + //@param eps : convergence criterion for refinement (step size in fractional pixels where, absolute precision is ~eps * pi / bw radians) + //@return : cross correlation of peak + virtual Real refinePeak(std::complex const * const gln, Real * const eu, const Real eps = Real(0.01)) = 0; + }; + + //@brief : dervied class to compute un-normalized cross correlation + template + struct UnNormalizedCorrelator : public PhaseCorrelator { + std::shared_ptr< std::vector< std::complex > > flm;//actual harmonics + const bool fMr;//z mirror flag + const size_t fNf;//n fold symmetry + + //@brief : construct a spherical correlator for a given bandwidth + //@param bandWidth: maximum bandwidth of spherical harmonic to use in correlation (exclusive) + //@param flm : spherical harmonic coefficients for the reference function + //@param fMr : true/false if there is/isn't a mirror plane in the reference fuction + //@param fNf : rotational symmetry about z axis in reference function (1 for no rotational symmetry) + UnNormalizedCorrelator(const size_t bandWidth, std::shared_ptr< std::vector< std::complex > > flm, const bool fMr, const size_t fNf) : PhaseCorrelator(bandWidth), flm(flm), fMr(fMr), fNf(fNf) {} + + //@brief : compute the rotation of the maximum cross correlation between two spherical functions + //@param gln: spherical harmonic coefficients for the template function + //@param eu : location to write rotation of maximum cross correlation as ZYZ euler angle + //@param ref: true/false to use/not use real space refinement + //@param eps: convergence criterion for refinement (step size in fractional pixels where, absolute precision is ~eps * pi / bw radians) + //@return : maximum + Real correlate(std::complex const * const gln, Real * const eu, const bool ref = true, const Real eps = Real(0.01)) {return Correlator::correlate(flm->data(), gln, fMr, fNf, eu, ref, eps);} + + //@brief : compute a subpixel maxima in the cross correlation by either interpolation or refinement + //@param gln : spherical harmonic coefficients for the second function + //@param eu : location to write rotation of maximum cross correlation as ZYZ euler angle + //@param eps : convergence criterion for refinement (step size in fractional pixels where, absolute precision is ~eps * pi / bw radians) + //@return : cross correlation of peak + Real refinePeak(std::complex const * const gln, Real * const eu, const Real eps = Real(0.01)) {return Correlator::refinePeak(flm->data(), gln, fMr, fNf, eu, eps);} + + //@brief : get a copy of the stored spherical correlator + //@return: unique pointer to copy of current correlator + std::unique_ptr > clone() const {return std::unique_ptr >(new UnNormalizedCorrelator(*this));} + }; + + //@brief : dervied class to compute normalized cross correlation + //@reference: Huhle, B., Schairer, T., and Strasser, W. (2009) Normalized Cross-Correlation Using SOFT + //@note : this class assumes the refernce function has full support + template + struct NormalizedCorrelator : public PhaseCorrelator { + struct Constants;//helper struct to hold read only constants + const std::shared_ptr ncLut;//read only values (can be shared across threads) + + //@brief : construct a spherical correlator for a given bandwidth + //@param bandWidth: maximum bandwidth of spherical harmonic to use in correlation (exclusive) + //@param flm : spherical harmonic coefficients for the reference function + //@param flm2 : spherical harmonic coefficients for the reference (function^2) + //@param fMr : true/false if there is/isn't a mirror plane in the reference fuction + //@param fNf : rotational symmetry about z axis in reference function (1 for no rotational symmetry) + //@param mlm : spherical harmonic coefficients for the mask function + NormalizedCorrelator(const size_t bandWidth, std::complex const * const flm, std::complex const * const flm2, const bool fMr, const size_t fNf, std::complex const * const mlm); + + //@brief : compute the rotation of the maximum normalized cross correlation between two spherical functions + //@param gln: spherical harmonic coefficients for the template function + //@param eu : location to write rotation of maximum normalized cross correlation as ZYZ euler angle + //@param ref: true/false to use/not use real space refinement + //@param eps: convergence criterion for refinement (step size in fractional pixels where, absolute precision is ~eps * pi / bw radians) + //@return : maximum (semi)normalized cross correlation [still needs to be divided by the standard deviation of the pattern function] + Real correlate(std::complex const * const gln, Real * const eu, const bool ref = true, const Real eps = Real(0.01)); + + //@brief : compute a subpixel maxima in the normalized cross correlation by either interpolation or refinement + //@param gln : spherical harmonic coefficients for the second function + //@param eu : location to write rotation of maximum cross correlation as ZYZ euler angle + //@param eps : convergence criterion for refinement (step size in fractional pixels where, absolute precision is ~eps * pi / bw radians) + //@return : cross correlation of peak + //@note : to be fully robust this should use the chain rule to account for the effects of shifting the window (which it doesn't currently do) + // not using the chain rule assumes that the effect of the window is small near the peak + Real refinePeak(std::complex const * const gln, Real * const eu, const Real eps = Real(0.01)); + + //@brief : get a copy of the stored spherical correlator + //@return: unique pointer to copy of current correlator + std::unique_ptr > clone() const {return std::unique_ptr >(new NormalizedCorrelator(*this));} + }; + } + +} + +//////////////////////////////////////////////////////////////////////// +// Implementations // +//////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "constants.hpp" +#include "wigner.hpp" +#include "util/linalg.hpp" + +namespace emsphinx { + + namespace sphere { + //helper struct to hold read only constants needed by Correlator (for sharing across threads) + template + struct Correlator::Constants { + //these members could be shared across threads since they should be read only after construction + // const size_t bw ;//maximum bandwidth to use (exclusive) + // const size_t sl ;//side length of grid in euler space (2 * bandWidth - 1) + std::vector< Real > wigD;//lookup table for wigner d functions + fft::SepRealFFT3D plan;//fftw plan to compute Real cross correlation from DFT(cross correlation + std::vector< Real > wigE;//lookup table for dTablePre + std::vector< Real > wigW;//lookup table for dTablePre + std::vector< Real > wigB;//lookup table for dTablePre + + //@brief : construct a set of constants for a given max bandwidth + //@param bw: maximum bandwidth of spherical harmonic to use in correlation (exclusive) + //@param fs: fft size (cube side length) + Constants(const size_t bw, const size_t fs); + }; + + //helper struct to hold additional read only constants needed by NormalizedCorrelator (for sharing across threads) + template + struct NormalizedCorrelator::Constants { + //these members could be shared across threads since they should be read only after construction + std::vector< Real > rDen;//reciprocal of denominator for normalization over euler angle grid (computed via equation 8 in reference) + fft::vector< std::complex > flm ;//spherical harmonic coefficients for the reference function + fft::vector< std::complex > flm2;//spherical harmonic coefficients for the reference (function^2) + fft::vector< std::complex > mlm ;//spherical harmonic coefficients for the mask function + bool mr ;//does flm have a mirror plane at the equator + size_t nf ;//rotational symmetry of flm about z axis + + //@brief : construct a spherical correlator for a given bandwidth + //@param cor : correlator object + //@param flm : spherical harmonic coefficients for the reference function + //@param flm2: spherical harmonic coefficients for the reference (function^2) + //@param fMr : true/false if there is/isn't a mirror plane in the reference fuction + //@param fNf : rotational symmetry about z axis in reference function (1 for no rotational symmetry) + //@param mlm : spherical harmonic coefficients for the mask function + Constants(NormalizedCorrelator* cor, std::complex const * const flm, std::complex const * const flm2, const bool fMr, const size_t fNf, std::complex const * const mlm); + + //@brief : compute the normalization denominator at an arbitrary rotation + //@param cor: correlator object + //@param eu : rotation to compute normalization for + //@return : normalization + Real denominator(Correlator& cor, Real * const eu) const; + }; + + namespace detail { + //@brief : compute product of ab * cd and ad * conj(cd) without duplicate flops + //@param ab: first complex number (a + bi) + //@param cd: second complex number (c + di) + //@param vp: location to write ab * cd + //@param vc: location to write ab * conj(cd) + template + inline void conjMult(const std::complex& ab, const std::complex& cd, std::complex& vp, std::complex& vc); + + //@brief : convert a vectorized index in the 3d cross correlation grid to individual components + //@param idx: vectorized index + //@param sl : side length of cube + //@param knm: location to write indices (z,y,x) + inline void extractInds(size_t idx, const size_t sl, size_t knm[3]); + + //@brief: interpolate subpixel peak location from a 3d voxel grid + //@param p: neighborhood around peak + //@param x: location to store subpixel maxima location within neighborhood (z, y, x from -1->1) + //@param return: value of fit quadratic at maxima + template + Real interpolateMaxima(PixelType p[3][3][3], Real x[3]); + } + + //@brief : construct a set of constants for a given max bandwidth + //@param bw: maximum bandwidth of spherical harmonic to use in correlation (exclusive) + //@param fs: fft size (cube side length) + template + Correlator::Constants::Constants(const size_t bw, const size_t fs) : + wigD(bw * bw * bw), + wigE(bw * bw ), + wigW(bw * bw * bw), + wigB(bw * bw * bw), + plan(fs, fft::flag::Plan::Patient)//the fft is the slowest part, find the best possible algorithm + { + wigner::dTable(bw, wigD.data(), true); + wigner::dTablePreBuild(bw, wigE.data(), wigW.data(), wigB.data()); + } + + template + Correlator::Correlator(const size_t bandWidth) : + bw (bandWidth ), + sl (2 * bw - 1 ), + slP ((size_t)fft::fastSize((uint32_t)sl) ), + bwP ( (slP) / 2 + 1 ), + xcLut(std::make_shared(bw, slP)), + fm (bw * bw ), + gn (bw ), + fxc (slP * slP * bwP ), + xc (slP * slP * bwP ), + dBeta(bw * bw * bw * 2 ) {} + + //@brief: compute the cross correlation between two spherical functions + //@param flm: spherical harmonic coefficients for the first function + //@param gln: spherical harmonic coefficients for the second function + //@param fMr: true/false if there is/isn't a mirror plane in the first fuction + //@param fNf: rotational symmetry about z axis in first function (1 for no rotational symmetry) + //@param eu : location to write rotation of maximum cross correlation as ZYZ euler angle + //@param ref: true/false to use/not use real space refinement + //@param eps: convergence criterion for refinement (step size in fractional pixels where, absolute precision is ~eps * pi / bw radians) + //@return : maximum cross correlation + template + Real Correlator::correlate(std::complex const * const flm, std::complex const * const gln, const bool fMr, const size_t fNf, Real * const eu, const bool ref, const Real eps) { + compute(flm, gln, fMr, fNf, xc.data());//compute cross correlation on euler grid + const size_t ind0 = findPeak();//find maximum cross correlation + const Real peak = interpPeak(ind0, eu); + return ref ? refinePeak(flm, gln, fMr, fNf, eu, eps) : peak;//refine peak if needed + } + + //@brief : compute a subpixel maxima in the cross correlation using interpolation + //@param ind0: pixel to search from (should be near a local maxima) + //@param eu : location to write rotation of maximum cross correlation as ZYZ euler angle + //@return : estimated cross correlation of peak + template + Real Correlator::interpPeak(size_t ind0, Real * const eu) { + //convert from vectored index to k,n,m indices + size_t knm[3]; + detail::extractInds(ind0, slP, knm); + + //compute indicies of neighborhood around peak with periodic boundary conditions + const size_t N = 1;//half kernel size + const size_t W = N * 2 + 1;//full kernel size + Real xCorr[W][W][W]; + extractNeighborhood(ind0, xCorr); + + //sub pixel interpolate peak using tri quadratic + Real x[3] = {0,0,0};//initial guess at maximum voxel (z,y,x) + Real peak0 = detail::interpolateMaxima(xCorr, x); + const Real xMax = std::max(std::fabs(x[0]), std::max(std::fabs(x[1]), std::fabs(x[0]))); + if(xMax > Real(1)) {//dont step too far in case we're near a degeneracy + std::fill(x, x + 3, Real(0)); + peak0 = xCorr[N][N][N]; + } + + //convert subpixel to ZYZ euler angle + eu[0] = ((Real(knm[2]) + x[2])*4 - slP) * emsphinx::Constants::pi / (2 * slP);//alpha + eu[1] = ((Real(knm[0]) + x[0])*2 - slP) * emsphinx::Constants::pi / ( slP);//beta + eu[2] = ((Real(knm[1]) + x[1])*4 - slP) * emsphinx::Constants::pi / (2 * slP);//gamma + return peak0; + } + + //@brief : compute a subpixel maxima in the cross correlation by either interpolation or refinement + //@param flm : spherical harmonic coefficients for the first function + //@param gln : spherical harmonic coefficients for the second function + //@param fMr : true/false if there is/isn't a mirror plane in the first fuction + //@param fNf : rotational symmetry about z axis in first function (1 for no rotational symmetry) + //@param eu : location to write rotation of maximum cross correlation as ZYZ euler angle + //@param eps : convergence criterion for refinement (step size in fractional pixels where, absolute precision is ~eps * pi / bw radians) + //@return : cross correlation of peak + template + Real Correlator::refinePeak(std::complex const * const flm, std::complex const * const gln, const bool fMr, const size_t fNf, Real * const eu, const Real eps) { + //initial setup + Real eu0[3] = {eu[0], eu[1], eu[2]};//save a copy of the original orientation + const Real absEps = eps * emsphinx::Constants::pi2 / slP ;//compute stopping criterion is factor of grid resolution + const Real euEps = std::sqrt(std::numeric_limits::epsilon());//epsilon for eu[1] being too close to 0 or pi + const size_t maxIter = 15;//in tests of perfect rotations convergence generally takes at most 3 iterations (some edge cases near degeneracies are slower) + Real hes[9], jac[3], step[3];//working space for hessian, jacobian, and euler update + Real prevMag2 = emsphinx::Constants::pi2 * 3 / slP;//first step better not be more than 1 pixel in each direction + + //newton's method refinement + try { + Real peak; + for(size_t iter = 1; iter <= maxIter; iter++) { + //build hessian and derivatives + peak = derivatives(flm, gln, eu, jac, hes, bw, fMr, fNf); + + //the hessian may be (nearly) non positive definate + try {//standard newton update computation + if(std::isnan(hes[4])) throw std::runtime_error("degenerate"); + solve::cholesky(hes, step, jac, 3);//solve for step (this will fail for non definate matricies but thats good since we don't want a saddle point) + const Real mag2 = step[0]*step[0] + step[1]*step[1] + step[2]*step[2];//compute step size + if(mag2 > prevMag2) throw std::runtime_error("newton steps must always decrease in magnitude");//so far only a observed near degeneracies + prevMag2 = mag2;//update step size + } catch(...) {//there was a problem with the newton update + //handle degeneracy near eu[1] = 0 and +/-pi + if(std::isnan(hes[4])) { + //if we're actually on the degeneracy the beta^2 derivative is undefined, do the 1x1 sub problem + step[0] = jac[0] / hes[0]; + step[1] = 0; + step[2] = 0; + } else {//not degenerate but probably close + //if we're just close to the degeneracy sovle 2x2 sub problem manually (3rd euler angle is false DoF) + const Real det = hes[0] * hes[4] - hes[1] * hes[1]; + if(det < euEps) { + if(std::fabs(det) < euEps) throw std::runtime_error("singular matrix during newton iteration");//don't divide by 0 this should be extremely rare + if(det < euEps) throw std::runtime_error("converging to saddle during newton iteration");//it isn't clear how often this can happen (limiting the step size prevents occurance in all testing) + } + step[0] = (jac[0] * hes[4] - jac[1] * hes[1]) / det; + step[1] = (jac[1] * hes[0] - jac[0] * hes[1]) / det; + step[2] = 0; + } + } + + //note that we've computed the step, apply and check for convergence + std::transform(eu, eu + 3, step, eu, std::minus());//apply step + std::transform(step, step + 3, step, [](const Real& i){return std::fabs(i);});//convert steps to absolute value + if(*std::max_element(step, step + 3) < absEps) break;//check for convergence + if(iter == maxIter) throw std::runtime_error("failed to converge during cross correlation refinement"); + } + return peak; + + } catch(...) { + //there are still occasional failures due to the degeneracies behaving as saddle points + std::copy(eu0, eu0 + 3, eu);//use original orientation + return derivatives(flm, gln, eu, NULL, NULL, bw, fMr, fNf, false);//compute cross correlation at eu + } + } + + //@brief : extract an (N+1)^3 neighborhood around a pixel using periodic boundary conditions + //@template N: half window size of neighborhood to extract + //@param idx : vectorized index to extract neighborhood around + //@param nh : 3D array of size (N+1)^3 to write neighborhood into + template + template + void Correlator::extractNeighborhood(const size_t idx, Real (&nh)[N*2+1][N*2+1][N*2+1]) const { + //convert from vectored index to k,n,m indices + size_t knm[3]; + detail::extractInds(idx, slP, knm); + + //compute indicies of neighborhood around peak with periodic boundary conditions + const size_t W = N * 2 + 1;//full kernel size + size_t inds[3][W]; + for(size_t i = 0; i < 3; i++) { + inds[i][N] = knm[i]; + for(size_t j = 0; j < N; j++) { + //fill in -j indicies in direction i with periodic boundary conditions + inds[i][N-1-j] = inds[i][N-j] == 0 ? slP - 1 : inds[i][N-j] - 1; + + //fill in +j indicies in direction i with periodic boundary conditions + inds[i][N+1+j] = inds[i][N+j] + 1; + if(inds[i][N+1+j] == slP) inds[i][N+1+j] = 0; + } + } + + //now use glide plane to bring top half points to bottom half + for(size_t i = 0; i < W; i++) { + if(inds[0][i] >= bwP) {//we're in the upper half of the euler cube + inds[2][i] = inds[2][i] < bwP ? inds[2][i] + bwP - 1 : inds[2][i] - bwP;//glide alpha + inds[1][i] = inds[1][i] < bwP ? inds[1][i] + bwP - 1 : inds[1][i] - bwP;//glide gamma + inds[0][i] = slP - inds[0][i];//glide beta + } + } + + //extract cross correlation from 3x3x3 grid around peak + for(size_t k = 0; k < W; k++) { + for(size_t n = 0; n < W; n++) { + for(size_t m = 0; m < W; m++) { + nh[k][n][m] = xc[inds[0][k]*slP*slP + inds[1][n]*slP + inds[2][m]]; + } + } + } + } + + //@brief : compute the index of the pixel closest to an orientation + //@param eu: ZYZ euler angle to find nearest pixel of + //@return : vectorized pixel index + template + size_t Correlator::eulerIndex(Real const * const eu) const { + //convert from euler angle to fractional indices + Real knmR[3] = { + ((eu[1] * ( slP)) / emsphinx::Constants::pi + slP ) / 2,//beta -> k + ((eu[2] * (2 * slP)) / emsphinx::Constants::pi + slP ) / 4,//gamma -> n + ((eu[0] * (2 * slP)) / emsphinx::Constants::pi + slP ) / 4,//alpha -> m + }; + + //move beta -> [-pi,0] if needed + const Real sl2 = Real(slP) / 2; + if(knmR[0] > sl2) {//we're in the upper half of the euler cube + knmR[0] = Real(slP) - knmR[0];//glide beta + knmR[1] = std::fmod(knmR[1] + sl2, slP);;//glide gamma + knmR[2] = std::fmod(knmR[2] + sl2, slP);;//glide gamma + } + + //convert to nearest neighbors + const size_t knm[3] = { + (size_t) std::round(knmR[0]), + (size_t) std::round(knmR[1]), + (size_t) std::round(knmR[2]), + }; + + //vectorize + return knm[0] * slP * slP + knm[1] * slP + knm[2]; + } + + //@brief : compute the orientation of an index + //@param idx: vectorized pixel index + //@param eu : location to write ZYZ euler angles + template + void Correlator::indexEuler(const size_t idx, Real * const eu) const { + //convert from vectorized to k,n,m + size_t knm[3]; + detail::extractInds(idx, slP, knm); + + //convert from knm to alpha,beta, gamma + eu[0] = (Real(knm[2])*4 - slP) * emsphinx::Constants::pi / (2 * slP);//alpha = 2 * pi * m / slP - pi / 2 + eu[1] = (Real(knm[0])*2 - slP) * emsphinx::Constants::pi / ( slP);//beta = 2 * pi * k / slP - pi + eu[2] = (Real(knm[1])*4 - slP) * emsphinx::Constants::pi / (2 * slP);//gamma = 2 * pi * n / slP - pi / 2 + } + + //@brief : copy the cross correlation from the previous call and convert to ZXZ euler angles with origin at 0 + //@param zxz: zxz euler angle grid to write cross correlation to + //@note : phi1 (rotation about Z) increments fastest, phi2 (rotation about Z'') middle, and Phi (rotation about X') slowest + template + void Correlator::extractBunge(Real * const zxz) const { + /* + //this is extremely poorly implemented as written to keep things obvious + //this hasn't been updated since zero padding was added + for(size_t k = 0; k < bw; k++) { + Real Phi = Real(k) / sl;//[0,0.5) + for(size_t m = 0; m < sl; m++) { + Real phi2 = Real(m) / sl;//[0,1) + phi2 -= Real(0.25);//convert from zxz -> zyz: [-0.25,0.75) + for(size_t n = 0; n < sl; n++) { + Real phi1 = Real(n) / sl;//[0,1) + phi1 += Real(0.25);//convert from zxz -> zyz: [0.25,1.25) + + //we now have fractional zyz euler angles, convert to fractional indices (correct for origin) + double rm = phi1 + Real(0.25);//[0.5,1.5) + double rk = Phi + Real(0.5 );//[0.5,1.0) + double rn = phi2 + Real(0.25);//[0.0,1.0) + + //now we have indices in the +beta half but we only have the -beta cube, move to bottom half using glide plane + rm = rm - Real(0.5);//glide alpha: [ 0.0,1.0) + rk = Real(1.0) - rk;//glide beta : ( 0.0,0.5] + rn = rn - Real(0.5);//glide gamma: [-0.5,0.5) + if(std::signbit(rn)) rn += Real(1.0);//[0.0,1.5) + + //finally convert to integer indices in our knm grid + size_t ik = (size_t)std::round(rk * sl);//, sl-1); + size_t in = (size_t)std::round(rn * sl);//, bw-1);//if rk == 0.5 we'll round up to bw but we can actually just mirror back instead of glide back + size_t im = (size_t)std::round(rm * sl);//, sl-1); + if(ik == bw) ik = bw - 1; + if(in == sl) in = 0; + if(im == sl) in = 0; + if(ik >= bw || in >= sl || im >= sl) continue; + + zxz[k * sl * sl + m * sl + n] = xc[ik * sl * sl + im * sl + in]; + } + } + } + */ + + //slightly more difficult to understand but significantly more efficient + //this isn't exactly correct since there is a half pixel shift (I picked the rounding direction that puts high correlation near the origin instead of the opposite corner) + Real * pOut = zxz;//copy pointer to output start + auto pIn = xc.cbegin() + bwP * slP * slP;//get pointer past last slice + for(size_t k = 0; k < bwP; k++) {//loop over z slices + pIn -= slP * slP;//move to previous slice start + for(size_t m = 0; m < slP; m++) {//loop over rows + const size_t im = ( slP - 1 - m + bwP + 1) % slP;//get index of row to copy + auto iter = pIn + im * slP;//get pointer to input row start + *pOut = *iter; + std::reverse_copy(iter + 1, iter + slP, pOut + 1);//mirror copy into output row + pOut += slP;//increment output + } + } + } + + //@brief : compute the cross correlation between two spherical functions + //@param flm: spherical harmonic coefficients for the first function + //@param gln: spherical harmonic coefficients for the second function + //@param fMr: true/false if there is/isn't a mirror plane in the first fuction + //@param fNf: rotational symmetry about z axis in first function (1 for no rotational symmetry) + //@param pXc: location to write cross correlation + template + void Correlator::compute(std::complex const * const flm, std::complex const * const gln, const bool fMr, const size_t fNf, Real * const pXc) { + //naive implementation (no symmetry) to make summation clear + //this also assumes no zero padding + /* + std::fill(fxc.begin(), fxc.end(), std::complex(0)); + const bool realFft = true;//true/false to use half sized fft format + const size_t dm = realFft ? bw : sl;//length of fastest indexing dimension + for(size_t ic = 0; ic < sl; ic++) { + const int k = ic >= bw ? int(ic) - sl : ic; + const size_t ak = std::abs(k); + for(size_t ib = 0; ib < sl; ib++) { + const int n = ib >= bw ? int(ib) - sl : ib; + const size_t an = std::abs(n); + const size_t maxKN = std::max(ak, an); + for(size_t ia = 0; ia < dm; ia++) { + const int m = ia >= bw ? int(ia) - sl : ia; + const size_t am = std::abs(m); + const size_t start = std::max(am, maxKN); + for(size_t j = start; j < bw; j++) { + const Real dlkm = wigD[ak * bw * bw + am * bw + j] * wigner::dSign(j, k, m);//wigner::d(j, k, m);//this is for trans=false + const Real dlnk = wigD[an * bw * bw + ak * bw + j] * wigner::dSign(j, n, k);//wigner::d(j, n, k);//this is for trans=false + const std::complex& vflm = flm[am * bw + j];//\hat{f}^l_{|m|} + const std::complex& vgln = gln[an * bw + j];//\hat{g}^l_{|n|} + const std::complex f = std::signbit(m) ? std::conj(vflm) * Real(0 == am % 2 ? 1 : -1) : vflm;//symmetry of real SHT coefficients + const std::complex g = std::signbit(n) ? std::conj(vgln) * Real(0 == an % 2 ? 1 : -1) : vgln;//symmetry of real SHT coefficients + fxc[ic * sl * dm + ib * dm + ia] += f * std::conj(g) * dlkm * dlnk; + } + } + } + } + */ + + //the above loop is conceptually simple but has many redundant calculations + //the loop that follows is mathematically equivalent (for SHT of real functions only!!) much faster: + // -use \hat{f}^l_{-m} = (-1)^m * \hat{f}^l_{m} for real valued functions (and the same for \hat{g}^l_{-n}) + // -build the fft of a real valued cross correlation + // -precompute values of \hat{f}^l_{m} * d^l_{k,m}(\frac{\pi}{2}) [stored in fm] + // -precompute values of \hat{g}^l_{n} * d^l_{n,k}(\frac{\pi}{2}) [stored in gn] + // -eliminate redundant calculations from f * g and f * conj(g) + + //save some useful values + const std::complex cz(0); + const size_t mBw = bw ;//maximum bandwidth to use in calculation (better be <= bw but could be smaller for slight speedup, effectively a top hat filter size) + const size_t flmFold = fNf ;//rotational symmetry of flm about z axis (flm[m*bw+j] == 0 if m % flmFold != 0) + const size_t glnFold = 1 ;//rotational symmetry of gln about z axis (gln[n*bw+j] == 0 if n % glnFold != 0) + const bool fMir = fMr ;//true/false if (flm[m*bw+j] == 0 if (m+j) % 2 != 0) + const bool gMir = false ;//true/false if (gln[n*bw+j] == 0 if (n+j) % 2 != 0) + const bool mirror = fMir || gMir;//is there at least 1 mirror + + //precompute locations of systemic zeros + //the storage for this should be moved to the constructor + std::vector mFoldN0(bwP); + for(size_t m = 0; m < mBw; m++) mFoldN0[m] = (uint_fast8_t) (0 != m % flmFold);//there are systemic zeros from rotational symmetry in flm + + //one value for each column for n % 2 == 0 and n % 2 == 1 + //true if cross correlation at n, m is nonzero, false if it is a systemic zero + //using an integer array is much faster than bools becuase of the vector specialization + std::vector nonZero0(bwP, false);//initialize with false for zero pad columns + std::vector nonZero1(bwP, false);//initialize with false for zero pad columns + const bool bMirror = fMir && gMir;//do both functions have a mirror + for(size_t m = 0; m < mBw; m++) { + const bool match0 = (m + 0) % 2 == 0;//check if parity of m matches parity of n == 0 + const bool match1 = (m + 1) % 2 == 0;//check if parity of m matches parity of n == 1 + const bool mir0 = bMirror && !match0;//there are systemic zeros if both functions have a mirror plane and a parity mismatch + const bool mir1 = bMirror && !match1;//there are systemic zeros if both functions have a mirror plane and a parity mismatch + const bool mFold = 0 != m % flmFold;//there are systemic zeros from rotational symmetry in flm + nonZero0[m] = !(mFold || mir0);//for n%2 == 0 + nonZero1[m] = !(mFold || mir1);//for n%2 == 1 + } + + //loop over planes + std::complex v, vnc, vp, vc; + std::complex* pK = fxc.data() ;//pointer to slice k + std::complex* nK = fxc.data() + slP * slP * bwP;//pointer to slice (slP - k) + std::complex *pKpN, *pKnN, *nKpN, *nKnN;//row pointers + for(size_t k = 0; k < mBw; k++) {//loop over non-zero z slices + //precompute flm values * wigner d function + //I transposed the wigner table from the simple (commented) loop to prioritize access for the inner loop + Real const * pWig = xcLut->wigD.data() + k * bw; + std::complex * pFm = fm .data(); + std::complex const * pFlm = flm ; + for(size_t m = 0; m < mBw; m++) { + for(size_t j = std::max(m, k); j < mBw; j++) pFm[j] = pFlm[j] * pWig[j];//f^j_m * wigner::d(j, k, m) + pWig += bw * bw; + pFm += bw ; + pFlm += bw ; + } + + //compute pointers to row starts + pKpN = pK ; + nKpN = nK ; + pKnN = pK + slP * bwP; + nKnN = nK + slP * bwP; + const bool posK = k > 0; + + //loop over rows + pWig = xcLut->wigD.data() + k * bw * bw; + std::complex const * pGln = gln; + for(size_t n = 0; n < bwP; n++) { + const bool posN = n > 0; + const bool posKN = posK && posN; + if(0 == n % glnFold && n < mBw) {//we haven't reached 0 padded rows and gln values are non-zero, compute dot product + //precompute gln values * wigner d function + const size_t maxKN = std::max(k, n); + for(size_t j = maxKN; j < mBw; j++) gn[j] = std::conj(pGln[j]) * pWig[j];//\hat{g}^j_n * wigner::d(j, n, k) + + //loop over columns + std::vector const & nonZero = n % 2 == 0 ? nonZero0 : nonZero1; + for(size_t m = 0; m < mBw; m++) { + const size_t indMatch = (m + n) % 2; + if(nonZero[m]) {//checking for systemic zeros from double mirror and parity mismatch here makes subsequent logic easy + //build a pair of values as dot product + const size_t m2 = m % 2; + v = vnc = std::complex(0); + size_t start = std::max(m, maxKN);//first valid j + if(mirror) {//there is a single mirror or double mirror with parity matching + if(fMir && (start + m) % 2 != 0) ++start;//if fm[start] == 0 skip to next value (first value is zero) + if(gMir && (start + n) % 2 != 0) ++start;//if gn[start] == 0 skip to next value (first value is zero) [for double mirrors no change here since parities match] + const bool toggle = (start + m) % 2 == 0;//we don't need to toggle since we're incrementing by 2 but we still may need to negate the result + for(size_t j = start; j < mBw; j+=2) { + //do complex multiplication components by hand to eliminate duplicate flops from multiplying with conjugate + detail::conjMult(fm[m * bw + j], gn[j], vp, vc); + v += vp;//pF[j] * gn[j] + vnc += vc;//pF[j] * std::conj(gn[j]) + } + if(!toggle) vnc = -vnc; + } else { + bool toggle = (start + m) % 2 == 0; + for(size_t j = start; j < mBw; j++) { + //do complex multiplication components by hand to eliminate duplicate flops from multiplying with conjugate + detail::conjMult(fm[m * bw + j], gn[j], vp, vc); + v += vp ;// pF[j] * gn[j] + vnc += toggle ? vc : -vc;//+/-pF[j] * std::conj(gn[j]) + toggle = !toggle; + } + } + if(!(k % 2 == 0)) vnc = -vnc;//correct for computing negative vnc depending on j/m parity + + //fill in symmetric values using symmetry from: wigner d function, sht of real signal, sht of real pattern + const bool match = 0 == indMatch; + if(posKN) { + pKpN [m] = v ;//fxc( k, n, m) + nKnN [m] = vnc;//fxc(-k, -n, m) + if(match) { + nKpN[m] = v ;//fxc(-k, n, m) + pKnN[m] = vnc;//fxc( k, -n, m) + } else { + nKpN[m] = -v ;//fxc(-k, n, m) + pKnN[m] = -vnc;//fxc( k, -n, m) + } + } else { + pKpN [m] = v ;//fxc( k, n, m) + if(match) { + if (posK) nKpN[m] = v ;//fxc(-k, n, m) + else if(posN) pKnN[m] = vnc;//fxc( k, -n, m) + } else { + if (posK) nKpN[m] = -v ;//fxc(-k, n, m) + else if(posN) pKnN[m] = -vnc;//fxc( k, -n, m) + } + } + } else {//in systemic zero + if(posKN) { + pKpN[m] = cz;//fxc( k, n, m) + nKpN[m] = cz;//fxc(-k, n, m) + pKnN[m] = cz;//fxc( k, -n, m) + nKnN[m] = cz;//fxc(-k, -n, m) + } else { + pKpN[m] = cz;//fxc( k, n, m) + if (posK) nKpN[m] = cz;//fxc(-k, n, m) + else if(posN) pKnN[m] = cz;//fxc( k, -n, m) + } + } + } + } else {//we're in a row of systemic zeros or zero padding + //fill in systemic zeros from rotational symmetry in gln + std::fill (pKpN, pKpN + bwP, cz);//fxc( k, n, m) + if(posKN) std::fill(nKnN, nKnN + bwP, cz);//fxc(-k, -n, m) + if(posK ) std::fill(nKpN, nKpN + bwP, cz);//fxc(-k, n, m) + if(posN ) std::fill(pKnN, pKnN + bwP, cz);//fxc( k, -n, m) + } + + //increment row pointers + pKpN += bwP; + nKpN += bwP; + pKnN -= bwP; + nKnN -= bwP; + pWig += bw ; + pGln += bw ; + } + + //increment slice pointers + pK += slP * bwP; + nK -= slP * bwP; + } + + //fill in zero pad slices + if(mBw < bwP) std::fill(pK, nK + slP * bwP, cz); + + //compute cross correlation via fft + xcLut->plan.inverse(fxc.data(), pXc, flmFold);//this skips systemic zeros and only computes the first half of euler space + } + + //@brief : find the maximum cross correlation grid point + //@return: index of maximum cross correlation from previous call to correlate + template + size_t Correlator::findPeak() { + // return std::distance(xc.cbegin(), std::max_element(xc.cbegin(), xc.cbegin() + slP * slP * bwP)); + + //profiler says this is noticeably faster: + size_t iMax = 0; + Real vMax = xc.front(); + for(size_t i = 0; i < xc.size(); i++) { + if(xc[i] > vMax) { + vMax = xc[i]; + iMax = i; + } + } + return iMax; + } + + //@brief : compute the first and second derivatives of the cross correlation at a single rotation + //@param flm: spherical harmonic coefficients for the first function + //@param gln: spherical harmonic coefficients for the second function + //@param eu : rotation to compute derivatives of cross correlation for as ZYZ euler angle + //@param jac: location to write jacobian of cross correlation {d/(d eu[0]), d/(d eu[1]), d/(d eu[2])} + //@param hes: location to write hessian (3x3 matrix as 9 component vector) of cross correlation hes_ij = d/(d eu[i]) * d/(d eu[j]) + //@param mBW: maximum bandwidth to use in calculation (must be <= bw) + //@param fMr: true/false if there is/isn't a mirror plane in the first fuction + //@param fNf: rotational symmetry about z axis in first function (1 for no rotational symmetry) + //@param der: true/false to compute derivatives/only cross correlation + //@return : cross correlation for rotation eu + template + Real Correlator::derivatives(std::complex const * const flm, std::complex const * const gln, Real const * const eu, Real * const jac, Real * const hes, const size_t mBW, const bool fMr, const size_t fNf, const bool der) { + //initialze terms with 0 + Real wrk[10] = {0};//correlation, jacobian, hessian as 00, 11, 22, 01, 12, 20 + + //bring middle euler angle to [-pi,pi] for wigner calculations + Real beta = std::fmod(eu[1], emsphinx::Constants::pi2); + if(beta > emsphinx::Constants::pi) + beta -= emsphinx::Constants::pi2; + else if(beta < -emsphinx::Constants::pi) + beta += emsphinx::Constants::pi2; + + //compute sin/cos of alpha/gamma once (multiple angles) + const Real sA = std::sin(eu[0]);//sin(alpha) + const Real cA = std::cos(eu[0]);//cos(alpha) + const Real sG = std::sin(eu[2]);//sin(gamma) + const Real cG = std::cos(eu[2]);//cos(gamma) + + //precompute some values for on the fly wigner (uppercase) D calculation + const Real t = std::cos(beta); + const bool deg = std::fabs(std::fabs(t) - Real(1)) < std::numeric_limits::epsilon();//is beta nearly +/- pi? + const bool nB = std::signbit(beta); + const Real csc = Real(1) / std::sqrt(Real(1) - t * t) * (nB ? -1 : 1);//csc(beta), cot(beta) is csc * t + // wigner::dTable (mBW, t, nB, dBeta.data());//compute wigner (lowercase) d(beta) once + wigner::dTablePre(mBW, t, nB, dBeta.data(), xcLut->wigE.data(), xcLut->wigW.data(), xcLut->wigB.data());//compute wigner (lowercase) d(beta) once + + //build symmetry information + const size_t flmFold = fNf;//rotational symmetry of flm about z axis (flm[m*bw+j] == 0 if m % flmFold != 0) + const size_t glnFold = 1 ;//rotational symmetry of gln about z axis (gln[n*bw+j] == 0 if n % glnFold != 0) + const bool fMir = fMr ;//true/false if (flm[m*bw+j] == 0 if (m+j) % 2 != 0) + const bool gMir = false;//true/false if (gln[n*bw+j] == 0 if (n+j) % 2 != 0) + const bool mirror = fMir || gMir;//is there at least 1 mirror + const bool bMirror = fMir && gMir;//do both functions have a mirror + const size_t dJ = fMir ? 2 : 1; + + //////////////////////////////////////// + // loop over one order // + //////////////////////////////////////// + Real uA[3] = {0, sA * 2, 1};//recursion coefficients for chebyshev polynomial U_n(sin(alpha)) + Real tA[3] = {0, cA , 1};//recursion coefficients for chebyshev polynomial T_n(cos(alpha)) + for(int m = 0; m < mBW; m++) { + //////////////////////////////////////// + // efficiently compute exp(I m alpha) // + //////////////////////////////////////// + //update chebyshev recursion and use to compute exp(I m alpha) + if(m < 2) {//use seed values for chebyshev recursion + uA[0] = m == 0 ? 0 : sA;//sin(alpha * m) + tA[0] = m == 0 ? 1 : cA;//cos(alpha * m) + } else {//use chebyshev recursion + //compute chebyshev polynomials(m, alpha) and multiple angle sin/cos + uA[0] = sA * uA[1] * 2 - uA[2];//U_m(x) = 2 * x * U_{m-1}(x) - U_{m-2}(x) + tA[0] = cA * tA[1] * 2 - tA[2];//T_m(x) = 2 * x * T_{m-1}(x) - T_{m-2}(x) + const Real sm = m % 2 == 0 ? uA[1] * cA * (((m/2)-1) % 2 == 0 ? 1 : -1) : (uA[0] - sA * uA[1]) * (((m-1)/2) % 2 == 0 ? 1 : -1);//cos(alpha * m) + const Real cm = tA[0];//cos(alpha * m) + + //update recursion and store values of sin/cos(alpha * m) + uA[2] = uA[1]; uA[1] = uA[0];//update recursion coefficients for chebyshev polynomial of the first kind + tA[2] = tA[1]; tA[1] = tA[0];//update recursion coefficients for chebyshev polynomial of the second kind + uA[0] = sm;//store multiple sin value for subsequent access + tA[0] = cm;//store multiple cos value for subsequent access + } + const bool mFold0 = 0 != m % flmFold;//there are systemic zeros from rotational symmetry in flm + if(mFold0) continue;//flm[m * bw + j] == 0 so there is nothing to accumulate (but we still needed to update the multiple angle recursion) + const std::complex expAlpha(tA[0], uA[0]);//exp(I m alpha) = cos(m * alpha) + I sin(m * alpha) + + //////////////////////////////////////// + // loop over other order // + //////////////////////////////////////// + Real uG[3] = {0, sG * 2, 1};//recursion coefficients for chebyshev polynomial U_n(sin(gamma)) + Real tG[3] = {0, cG , 1};//recursion coefficients for chebyshev polynomial T_n(cos(gamma)) + + for(int n = 0; n < mBW; n++) { + //////////////////////////////////////// + // efficiently compute exp(I n gamma) // + //////////////////////////////////////// + //update chebyshev recursion and use to compute exp(I n gamma) + if(n < 2) {//use seed values for chebyshev recursion + uG[0] = n == 0 ? 0 : sG;//sin(gamma * n) + tG[0] = n == 0 ? 1 : cG;//cos(gamma * n) + } else {//use chebyshev recursion + //compute chebyshev polynomials(n, gamma) and multiple angle sin/cos + uG[0] = sG * uG[1] * 2 - uG[2];//U_n(x) = 2 * x * U_{n-1}(x) - U_{n-2}(x) + tG[0] = cG * tG[1] * 2 - tG[2];//T_n(x) = 2 * x * T_{n-1}(x) - T_{n-2}(x) + const Real sn = n % 2 == 0 ? uG[1] * cG * (((n/2)-1) % 2 == 0 ? 1 : -1) : (uG[0] - sG * uG[1]) * (((n-1)/2) % 2 == 0 ? 1 : -1);//cos(gamma * n) + const Real cn = tG[0];//cos(gamma * n) + + //update recursion and store values of sin/cos(gamma * n) + uG[2] = uG[1]; uG[1] = uG[0];//update recursion coefficients for chebyshev polynomial of the first kind + tG[2] = tG[1]; tG[1] = tG[0];//update recursion coefficients for chebyshev polynomial of the second kind + uG[0] = sn;//store multiple sin value for subsequent access + tG[0] = cn;//store multiple cos value for subsequent access + } + const bool nFold0 = 0 != n % glnFold;//there are systemic zeros from rotational symmetry in flm + if(nFold0) continue;//gln[n * bw + j] == 0 so there is nothing to accumulate (but we still needed to update the multiple angle recursion) + const std::complex expGamma(tG[0], uG[0]);//exp(I n gamma) = cos(n * gamma) + I sin(n * gamma) + + //handle the case of 2 mirror planes with different parity + const bool match = (m + n) % 2 == 0;//check if parity of m matches parity of n + const bool mir0 = bMirror && !match;//there are systemic zeros if both functions have a mirror plane and a parity mismatch + if(mir0) continue;//flm * gln = 0 for any l (alternating between flm and gln being 0) + + //////////////////////////////////////// + // compute degree independent terms // + //////////////////////////////////////// + //compute exp(I * +m * Alpha) * exp(I * +/-n * Gamma) for on the fly wigner (uppercase) D calculation + std::complex agP, agN;//exp(I * +m * Alpha) * exp(I * +/-n * Gamma) + detail::conjMult(expAlpha, expGamma, agP, agN); + const Real sign = (n+m)%2 == 0 ? 1 : -1; + const Real sn = Real(n%2 == 0 ? 1 : -1); + agP *= sign; + agN *= sign * sn; + + //get loop start + size_t start = std::max(m, n); + if(fMir && (start + m) % 2 != 0) ++start;//if fm[start] == 0 skip to next value (first value is zero) + if(gMir && (start + n) % 2 != 0) ++start;//if gn[start] == 0 skip to next value (first value is zero) [for double mirrors no change here since parities match] + + if(der) {//we need derivatives + //compute some prefactors for calculating derivatives of d^j_{m,n}(beta) + const int mm = m * m; + const int mn = m * n; + const int nn = n * n; + const Real coef2_0a = t * t * mm + (nn - m) ; + const Real coef2_0b = t * n * (1 - 2 * m) ; + const Real coef2_1a = t * (1 + 2 * m) ; + const Real coef1_0PP = ( t * m - n ) * csc ;//this is infinity if degenerate + const Real coef1_0PN = ( t * m + n ) * csc ;//this is infinity if degenerate + const Real coef2_0PP = (coef2_0a + coef2_0b) * csc * csc;//this is infinity if degenerate + const Real coef2_0PN = (coef2_0a - coef2_0b) * csc * csc;//this is infinity if degenerate + const Real coef2_1PP = (coef2_1a - 2 * n ) * csc ;//this is infinity if degenerate + const Real coef2_1PN = (coef2_1a + 2 * n ) * csc ;//this is infinity if degenerate + + //////////////////////////////////////// + // loop over degrees accumulating // + //////////////////////////////////////// + for(size_t j = start; j < mBW; j+=dJ) {//increment by 1 for no mirror planes 2 if any are present + //get wigner d^j_{m,+/-n} components + const Real d0P = dBeta[((m ) * mBW * mBW + n * mBW + j) * 2 + 0];//d^j_{m ,n}( beta) + const Real d0N = dBeta[((m ) * mBW * mBW + n * mBW + j) * 2 + 1];//d^j_{m ,n}(pi - beta) + const Real d0P_1 = m >= j ? 0 : dBeta[((m+1) * mBW * mBW + n * mBW + j) * 2 + 0];//d^j_{m+1,n}( beta) + const Real d0N_1 = m >= j ? 0 : dBeta[((m+1) * mBW * mBW + n * mBW + j) * 2 + 1];//d^j_{m+1,n}(pi - beta) + const Real d0P_2 = m+1 >= j ? 0 : dBeta[((m+2) * mBW * mBW + n * mBW + j) * 2 + 0];//d^j_{m+2,n}( beta) + const Real d0N_2 = m+1 >= j ? 0 : dBeta[((m+2) * mBW * mBW + n * mBW + j) * 2 + 1];//d^j_{m+2,n}(pi - beta) + + //compute derivatives of d^j_{m,+/-n}(beta) + const Real rjm = std::sqrt( Real( (j - m ) * (j + m + 1) ) ); + const Real coef2_2 = std::sqrt( Real( (j - m - 1) * (j + m + 2) ) ) * rjm; + const Real d1P = d0P * coef1_0PP - d0P_1 * rjm ;//first derivative of d^j_{+m,+n}(beta) w.r.t. beta + const Real d1N = d0N * coef1_0PN + d0N_1 * rjm ;//first derivative of d^j_{+m,-n}(beta) w.r.t. beta + const Real d2P = d0P * coef2_0PP - d0P_1 * rjm * coef2_1PP + d0P_2 * coef2_2;//second derivative of d^j_{+m,+n}(beta) w.r.t. beta + const Real d2N = d0N * coef2_0PN + d0N_1 * rjm * coef2_1PN + d0N_2 * coef2_2;//second derivative of d^j_{+m,-n}(beta) w.r.t. beta + + //compute f^l_m * g^l_n + std::complex vp, vc;//\hat{f}^l_{+m} * hat{g}^l_{+n} and \hat{f}^l_{+m} * conj(hat{g}^l_{+n}) + detail::conjMult(flm[m * bw + j], gln[n * bw + j], vp, vc);//do complex multiplication components by hand to eliminate duplicate flops from multiplying with conjugate + if((j+m)%2 != 0) vp = -vp; + + //compute components of cross correlation and partials + const std::complex vcPP = vc * agP;//+n correlation term prefactor: \hat{f}^l_{+m} * conj(hat{g}^l_{+n}) * exp(I m alpha + I n gamma) + const std::complex vcPP0 = vcPP * d0P;//+n correlation term: \hat{f}^l_{+m} * conj(hat{g}^l_{+n}) * D^l_{+m,+n}(alpha, beta, gamma) + const std::complex vcPP1 = vcPP * d1P;//beta partial of +n correlation term + const std::complex vpPN = vp * agN;//+n correlation term prefactor: \hat{f}^l_{+m} * conj(hat{g}^l_{-n}) * exp(I m alpha - I n gamma) + const std::complex vpPN0 = vpPN * d0N;//-n correlation term: \hat{f}^l_{+m} * conj(hat{g}^l_{-n}) * D^l_{+m,-n}(alpha, beta, gamma) + const std::complex vpPN1 = vpPN * d1N;//beta partial of -n correlation term + + //compute contributions to cross correlation, jacobian, and hessian from +m,+n (the real part of contributions from -m,-n are the same for real functions) + const Real xc[10] = { + vcPP0.real(), //cross correlation + vcPP0.imag() * -m , vcPP1.real() , vcPP0.imag() * -n ,//alpha , beta , gamma derivatives + vcPP0.real() * -mm, vcPP .real() * d2P, vcPP0.real() * -nn,//alpha^2, beta^2, gamma^2 derivatives + vcPP1.imag() * -m , vcPP1.imag() * -n , vcPP0.real() * -mn //alpha beta, beta gamma, gamma alpha derivatives + }; + + //compute contributions to cross correlation, jacobian, and hessian from +m,-n (the real part of contributions from -m,+n are the same for real functions) + const Real xp[10] = { + vpPN0.real(), //cross correlation + vpPN0.imag() * -m , vpPN1.real() , vpPN0.imag() * n ,//alpha , beta , gamma derivatives + vpPN0.real() * -mm, vpPN .real() * d2N, vpPN0.real() * -nn,//alpha^2, beta^2, gamma^2 derivatives + vpPN1.imag() * -m , vpPN1.imag() * n , vpPN0.real() * mn //alpha beta, beta gamma, gamma alpha derivatives + }; + + //accumulate contributions + std::transform(wrk, wrk + 10, xc, wrk, std::plus());//+m,+n + if(n > 0) std::transform(wrk, wrk + 10, xp, wrk, std::plus());//+m,-n + if(m > 0) { + std::transform(wrk, wrk + 10, xp, wrk, std::plus());//-m,+n + if(n > 0) std::transform(wrk, wrk + 10, xc, wrk, std::plus());//-m,-n + } + } + } else {//we only need cross correlation + for(size_t j = start; j < mBW; j+=dJ) {//increment by 1 for no mirror planes 2 if any are present + //get wigner d^j_{m,+/-n} components + const Real& d0P = dBeta[((m ) * mBW * mBW + n * mBW + j) * 2 + 0];//d^j_{m ,n}( beta) + const Real& d0N = dBeta[((m ) * mBW * mBW + n * mBW + j) * 2 + 1];//d^j_{m ,n}(pi - beta) + + //compute f^l_m * g^l_n + std::complex vp, vc;//\hat{f}^l_{+m} * hat{g}^l_{+n} and \hat{f}^l_{+m} * conj(hat{g}^l_{+n}) + detail::conjMult(flm[m * bw + j], gln[n * bw + j], vp, vc);//do complex multiplication components by hand to eliminate duplicate flops from multiplying with conjugate + if((j+m)%2 != 0) vp = -vp; + + //compute contributions to cross correlation + const Real vcPP0 = vc.real() * agP.real() - vc.imag() * agP.imag();//(vc * agP).real() + const Real vpPN0 = vp.real() * agN.real() - vp.imag() * agN.imag();//(vp * agN).real() + const Real xc = vcPP0 * d0P;//from +m,+n + const Real xp = vpPN0 * d0N;//from +m,-n + + //accumulate contributions + wrk[0] += xc;//+m,+n + if(n > 0) wrk[0] += xp;//+m,-n + if(m > 0) { + wrk[0] += xp;//-m,+n + if(n > 0) wrk[0] += xc;//-m,-n + } + } + } + } + } + + //copy result to outputs and return correlation + if(der) { + std::copy(wrk + 1, wrk + 4, jac); + hes[0] = wrk[4+0]; hes[1] = wrk[4+3]; hes[2] = wrk[4+5]; + hes[4] = wrk[4+1]; hes[5] = wrk[4+4]; + hes[8] = wrk[4+2]; + hes[3] = hes[1]; + hes[6] = hes[2]; hes[7] = hes[5]; + } + return wrk[0]; + } + + //@brief : construct a spherical correlator for a given bandwidth + //@param bandWidth: maximum bandwidth of spherical harmonic to use in correlation (exclusive) + //@param flm : spherical harmonic coefficients for the reference function + //@param flm2 : spherical harmonic coefficients for the reference (function^2) + //@param fMr : true/false if there is/isn't a mirror plane in the reference fuction + //@param fNf : rotational symmetry about z axis in reference function (1 for no rotational symmetry) + //@param mlm : spherical harmonic coefficients for the mask function + template + NormalizedCorrelator::NormalizedCorrelator(const size_t bandWidth, std::complex const * const flm, std::complex const * const flm2, const bool fMr, const size_t fNf, std::complex const * const mlm) : + PhaseCorrelator::PhaseCorrelator(bandWidth), + ncLut(std::make_shared(this, flm, flm2, fMr, fNf, mlm)) {} + + //@brief : compute the rotation of the maximum normalized cross correlation between two spherical functions + //@param gln: spherical harmonic coefficients for the template function + //@param eu : location to write rotation of maximum normalized cross correlation as ZYZ euler angle + //@param ref: true/false to use/not use real space refinement + //@param eps: convergence criterion for refinement (step size in fractional pixels where, absolute precision is ~eps * pi / bw radians) + //@return : maximum (semi)normalized cross correlation [still needs to be divided by the standard deviation of the pattern function] + template + Real NormalizedCorrelator::correlate(std::complex const * const gln, Real * const eu, const bool ref, const Real eps) { + //compute cross correlation on euler grid + Correlator::compute(ncLut->flm.data(), gln, ncLut->mr, ncLut->nf, Correlator::xc.data()); + + //normalize and find peak in single pass + size_t iMax = 0; + Real vMax = Correlator::xc.front() * ncLut->rDen.front(); + for(size_t i = 0; i < Correlator::xc.size(); i++) { + Real& vI = Correlator::xc[i];//get value + vI *= ncLut->rDen[i];//normalize + if(vI > vMax) {//check for new max + vMax = vI; + iMax = i; + } + } + + //refine peak + const Real peak = Correlator::interpPeak(iMax, eu); + return ref ? NormalizedCorrelator::refinePeak(gln, eu, eps) : peak; + } + + //@brief : compute a subpixel maxima in the normalized cross correlation by either interpolation or refinement + //@param gln : spherical harmonic coefficients for the second function + //@param eu : location to write rotation of maximum cross correlation as ZYZ euler angle + //@param eps : convergence criterion for refinement (step size in fractional pixels where, absolute precision is ~eps * pi / bw radians) + //@return : cross correlation of peak + //@note : to be fully robust this should use the chain rule to account for the effects of shifting the window (which it doesn't currently do) + // not using the chain rule assumes that the effect of the window is small near the peak + template + Real NormalizedCorrelator::refinePeak(std::complex const * const gln, Real * const eu, const Real eps) { + const Real cor = Correlator::refinePeak(ncLut->flm.data(), gln, ncLut->mr, ncLut->nf, eu, eps);//find unnormalized peak + return cor / ncLut->denominator(*this, eu);//normalize peak + } + + //@brief : construct a spherical correlator for a given bandwidth + //@param cor : correlator object + //@param flm : spherical harmonic coefficients for the reference function + //@param flm2: spherical harmonic coefficients for the reference (function^2) + //@param fMr : true/false if there is/isn't a mirror plane in the reference fuction + //@param fNf : rotational symmetry about z axis in reference function (1 for no rotational symmetry) + //@param mlm : spherical harmonic coefficients for the mask function + template + NormalizedCorrelator::Constants::Constants(NormalizedCorrelator* cor, std::complex const * const flm, std::complex const * const flm2, const bool fMr, const size_t fNf, std::complex const * const mlm) : + rDen(cor->getCubeSize() * cor->getCubeSize() * cor->getHalfSize()),//allocate space for denominator + flm (flm , flm + cor->bw * cor->bw ),//save reference spectra + flm2(flm2, flm2 + cor->bw * cor->bw ),//save reference^2 spectra + mlm (mlm , mlm + cor->bw * cor->bw ),//save mask spectra + mr (fMr ),//save reference mirror + nf (fNf ) //save reference rotational symmetry + { + //first compute window function correlated with reference function + cor->compute(flm, mlm, mr, nf, rDen.data());//store result in rDen array + + //next compute window function correlated with reference function^2 + cor->compute(flm2, mlm, mr, nf, cor->xc.data());//store result in working array of cor + + //finally compute the integral of the window function + const Real s2m = mlm[0].real() * std::sqrt(emsphinx::Constants::pi * Real(4));//assumes the window function is a binary mask [0,4pi] + + //now we have all the parts needed to fill in the first part of the denominator (equation 8) + std::transform(rDen.cbegin(), rDen.cend(), cor->xc.cbegin(), rDen.begin(), [s2m](const Real& mrf, const Real& mrf2){ + const Real fWbar = mrf / s2m;//equation 9 + return Real(1) / std::sqrt(mrf2 - Real(2) * fWbar * mrf + fWbar * fWbar * s2m);//compute reciprocal once to convert subsequent divisions into multiplications + }); + } + + //@brief : compute the normalization denominator at an arbitrary rotation + //@param cor: correlator object + //@param eu : rotation to compute normalization for + //@return : normalization + template + Real NormalizedCorrelator::Constants::denominator(Correlator& cor, Real * const eu) const { + //first compute window function correlated with reference function + const Real mrf = cor.derivatives(flm.data(), mlm.data(), eu, NULL, NULL, cor.getBw(), mr, nf, false); + + //next compute window function correlated with reference function^2 + const Real mrf2 = cor.derivatives(flm2.data(), mlm.data(), eu, NULL, NULL, cor.getBw(), mr, nf, false); + + //finally compute the integral of the window function + // const Real s2m = mlm[0].real() / std::sqrt(emsphinx::Constants::pi * Real(4));//assumes the window function is a binary mask, {fractional from [0,1]} + const Real s2m = mlm[0].real() * std::sqrt(emsphinx::Constants::pi * Real(4));//assumes the window function is a binary mask [0,4pi] + + //now we have all the parts needed to compute the denominator (equation 8) + const Real fWbar = mrf / s2m; + return std::sqrt(mrf2 - Real(2) * fWbar * mrf + fWbar * fWbar * s2m); + } + + namespace detail { + //@brief : compute product of ab * cd and ad * conj(cd) without duplicate flops + //@param ab: first complex number (a + bi) + //@param cd: second complex number (c + di) + //@param vp: location to write ab * cd + //@param vc: location to write ab * conj(cd) + template + inline void conjMult(const std::complex& ab, const std::complex& cd, std::complex& vp, std::complex& vc) { + T rr = ab.real() * cd.real();//ac (real * real) + T ii = ab.imag() * cd.imag();//bd (imag * imag) + T ri = ab.real() * cd.imag();//ad (real * imag) + T ir = ab.imag() * cd.real();//bc (imag * real) + vp.real(rr - ii); + vc.real(rr + ii); + vp.imag(ir + ri); + vc.imag(ir - ri); + } + + //@brief : convert a vectorized index in the 3d cross correlation grid to individual components + //@param idx: vectorized index + //@param sl : side length of cube + //@param knm: location to write indices (z,y,x) + inline void extractInds(size_t idx, const size_t sl, size_t knm[3]) { + knm[0] = idx / (sl * sl);//k component of idx + idx -= knm[0] * sl * sl; + knm[1] = idx / sl;//n component of idx + idx -= knm[1] * sl; + knm[2] = idx;//m component of idx + } + + //@brief: interpolate subpixel peak location from a 3d voxel grid + //@param p: neighborhood around peak + //@param x: location to store subpixel maxima location within neighborhood (z, y, x from -1->1) + //@param return: value of fit quadratic at maxima + template + Real interpolateMaxima(PixelType p[3][3][3], Real x[3]) { + //compute the 27 biquadradic coefficients, f(x,y,z) = a_{kji} x^i y^j z^k + //f(0, 0, 0) == a000 + const Real a000 = Real(p[1][1][1]); + + //f(1,0,0) = a000 + a100 + a200 && f(-1,0,0) = a000 - a100 + a200 + const Real a001 = Real(p[1][1][2] - p[1][1][0]) / 2; + const Real a002 = Real(p[1][1][2] + p[1][1][0]) / 2 - a000; + + //same relationships for y and z + const Real a010 = Real(p[1][2][1] - p[1][0][1]) / 2; + const Real a020 = Real(p[1][2][1] + p[1][0][1]) / 2 - a000; + const Real a100 = Real(p[2][1][1] - p[0][1][1]) / 2; + const Real a200 = Real(p[2][1][1] + p[0][1][1]) / 2 - a000; + + //f( 1, 1,0) = a000 + a100 + a200 + a010 + a020 + a110 + a210 + a120 + a220 + //f( 1,-1,0) = a000 + a100 + a200 - a010 + a020 - a110 - a210 + a120 + a220 + //f(-1, 1,0) = a000 - a100 + a200 + a010 + a020 - a110 + a210 - a120 + a220 + //f(-1,-1,0) = a000 - a100 + a200 - a010 + a020 + a110 - a210 - a120 + a220 + // --> f( 1, 1,0) + f( 1,-1,0) + f(-1, 1,0) + f(-1,-1,0) = 4 * (a000 + a020 + a200 + a220) + // --> f( 1, 1,0) - f( 1,-1,0) - f(-1, 1,0) + f(-1,-1,0) = 4 * a110 + // --> f( 1, 1,0) - f( 1,-1,0) + f(-1, 1,0) - f(-1,-1,0) = 4 * (a100 + a120) + // --> f( 1, 1,0) + f( 1,-1,0) - f(-1, 1,0) - f(-1,-1,0) = 4 * (a010 + a210) + const Real a022 = Real(p[1][2][2] + p[1][2][0] + p[1][0][2] + p[1][0][0]) / 4 - a000 - a020 - a002; + const Real a011 = Real(p[1][2][2] - p[1][2][0] - p[1][0][2] + p[1][0][0]) / 4; + const Real a012 = Real(p[1][2][2] + p[1][2][0] - p[1][0][2] - p[1][0][0]) / 4 - a010; + const Real a021 = Real(p[1][2][2] - p[1][2][0] + p[1][0][2] - p[1][0][0]) / 4 - a001; + + //same relationships for yz and zx + const Real a220 = Real(p[2][2][1] + p[2][0][1] + p[0][2][1] + p[0][0][1]) / 4 - a000 - a200 - a020; + const Real a110 = Real(p[2][2][1] - p[2][0][1] - p[0][2][1] + p[0][0][1]) / 4; + const Real a120 = Real(p[2][2][1] + p[2][0][1] - p[0][2][1] - p[0][0][1]) / 4 - a100; + const Real a210 = Real(p[2][2][1] - p[2][0][1] + p[0][2][1] - p[0][0][1]) / 4 - a010; + const Real a202 = Real(p[2][1][2] + p[0][1][2] + p[2][1][0] + p[0][1][0]) / 4 - a000 - a002 - a200; + const Real a101 = Real(p[2][1][2] - p[0][1][2] - p[2][1][0] + p[0][1][0]) / 4; + const Real a201 = Real(p[2][1][2] + p[0][1][2] - p[2][1][0] - p[0][1][0]) / 4 - a001; + const Real a102 = Real(p[2][1][2] - p[0][1][2] + p[2][1][0] - p[0][1][0]) / 4 - a100; + + //similar relationships for corners + const Real a222 = Real(p[2][2][2] + p[0][0][0] + p[0][2][2] + p[2][0][2] + p[2][2][0] + p[2][0][0] + p[0][2][0] + p[0][0][2]) / 8 - a000 - a200 - a020 - a002 - a022 - a202 - a220; + const Real a211 = Real(p[2][2][2] + p[0][0][0] + p[0][2][2] - p[2][0][2] - p[2][2][0] + p[2][0][0] - p[0][2][0] - p[0][0][2]) / 8 - a011; + const Real a121 = Real(p[2][2][2] + p[0][0][0] - p[0][2][2] + p[2][0][2] - p[2][2][0] - p[2][0][0] + p[0][2][0] - p[0][0][2]) / 8 - a101; + const Real a112 = Real(p[2][2][2] + p[0][0][0] - p[0][2][2] - p[2][0][2] + p[2][2][0] - p[2][0][0] - p[0][2][0] + p[0][0][2]) / 8 - a110; + const Real a111 = Real(p[2][2][2] - p[0][0][0] - p[0][2][2] - p[2][0][2] - p[2][2][0] + p[2][0][0] + p[0][2][0] + p[0][0][2]) / 8; + const Real a122 = Real(p[2][2][2] - p[0][0][0] - p[0][2][2] + p[2][0][2] + p[2][2][0] + p[2][0][0] - p[0][2][0] - p[0][0][2]) / 8 - a100 - a120 - a102; + const Real a212 = Real(p[2][2][2] - p[0][0][0] + p[0][2][2] - p[2][0][2] + p[2][2][0] - p[2][0][0] + p[0][2][0] - p[0][0][2]) / 8 - a010 - a012 - a210; + const Real a221 = Real(p[2][2][2] - p[0][0][0] + p[0][2][2] + p[2][0][2] - p[2][2][0] - p[2][0][0] - p[0][2][0] + p[0][0][2]) / 8 - a001 - a201 - a021; + + //newton iterate to find maxima + x[0] = x[1] = x[2] = 0;//initial guess at maximum voxel (z,y,x) + const size_t maxIter = 25; + const Real eps = std::sqrt(std::numeric_limits::epsilon()); + for(size_t i = 0; i < maxIter; i++) { + //compute components of hessian matrix + const Real xx = x[0] * x[0]; const Real yy = x[1] * x[1]; const Real zz = x[2] * x[2]; + const Real xy = x[0] * x[1]; const Real yz = x[1] * x[2]; const Real zx = x[2] * x[0]; + const Real h00 = (a200 + a210 * x[1] + a201 * x[2] + a220 * yy + a202 * zz + a211 * yz + a221 * yy * x[2] + a212 * x[1] * zz + a222 * yy * zz) * 2; + const Real h11 = (a020 + a021 * x[2] + a120 * x[0] + a022 * zz + a220 * xx + a121 * zx + a122 * zz * x[0] + a221 * x[2] * xx + a222 * zz * xx) * 2; + const Real h22 = (a002 + a102 * x[0] + a012 * x[1] + a202 * xx + a022 * yy + a112 * xy + a212 * xx * x[1] + a122 * x[0] * yy + a222 * xx * yy) * 2; + const Real h01 = a110 + a111 * x[2] + a112 * zz + (a210 * x[0] + a120 * x[1] + a211 * zx + a121 * yz + a212 * x[0] * zz + a122 * x[1] * zz + (a220 * xy + a221 * xy * x[2] + a222 * xy * zz) * 2) * 2; + const Real h12 = a011 + a111 * x[0] + a211 * xx + (a021 * x[1] + a012 * x[2] + a121 * xy + a112 * zx + a221 * x[1] * xx + a212 * x[2] * xx + (a022 * yz + a122 * yz * x[0] + a222 * yz * xx) * 2) * 2; + const Real h02 = a101 + a111 * x[1] + a121 * yy + (a102 * x[2] + a201 * x[0] + a112 * yz + a211 * xy + a122 * x[2] * yy + a221 * x[0] * yy + (a202 * zx + a212 * zx * x[1] + a222 * zx * yy) * 2) * 2; + + //build inverse of hessian matrix + const Real det = h00 * h11 * h22 - h00 * h12 * h12 - h11 * h02 * h02 - h22 * h01 * h01 + h01 * h12 * h02 * 2; + const Real i00 = (h11 * h22 - h12 * h12) / det; + const Real i11 = (h22 * h00 - h02 * h02) / det; + const Real i22 = (h00 * h11 - h01 * h01) / det; + const Real i01 = (h02 * h12 - h01 * h22) / det; + const Real i12 = (h01 * h02 - h12 * h00) / det; + const Real i02 = (h12 * h01 - h02 * h11) / det; + + //compute gradient + const Real d0 = a100 + a110 * x[1] + a101 * x[2] + a120 * yy + a102 * zz + a111 * yz + a121 * yy * x[2] + a112 * x[1] * zz + a122 * yy * zz + x[0] * (a200 + a210 * x[1] + a201 * x[2] + a220 * yy + a202 * zz + a211 * yz + a221 * yy * x[2] + a212 * x[1] * zz + a222 * yy * zz) * 2; + const Real d1 = a010 + a011 * x[2] + a110 * x[0] + a012 * zz + a210 * xx + a111 * zx + a112 * zz * x[0] + a211 * x[2] * xx + a212 * zz * xx + x[1] * (a020 + a021 * x[2] + a120 * x[0] + a022 * zz + a220 * xx + a121 * zx + a122 * zz * x[0] + a221 * x[2] * xx + a222 * zz * xx) * 2; + const Real d2 = a001 + a101 * x[0] + a011 * x[1] + a201 * xx + a021 * yy + a111 * xy + a211 * xx * x[1] + a121 * x[0] * yy + a221 * xx * yy + x[2] * (a002 + a102 * x[0] + a012 * x[1] + a202 * xx + a022 * yy + a112 * xy + a212 * xx * x[1] + a122 * x[0] * yy + a222 * xx * yy) * 2; + + //update x + const Real step[3] = { + i00 * d0 + i01 * d1 + i02 * d2, + i01 * d0 + i11 * d1 + i12 * d2, + i02 * d0 + i12 * d1 + i22 * d2 + }; + x[0] -= step[0]; x[1] -= step[1]; x[2] -= step[2]; + + //check for convergence + const Real maxStep = std::max(std::max(std::fabs(step[0]), std::fabs(step[1])), std::fabs(step[2])); + if(maxStep < eps) break; + if(i+1 == maxIter) std::fill(x, x+3, Real(0));//don't interpolate if convergence wasn't reached + } + + //compute interpolated value of maxima + const Real xx = x[0] * x[0]; const Real yy = x[1] * x[1]; const Real zz = x[2] * x[2]; + const Real xy = x[0] * x[1]; const Real yz = x[1] * x[2]; const Real zx = x[2] * x[0]; + const Real vPeak = a000 + a111 * x[0] * x[1] * x[2] + a222 * xx * yy * zz + + a100 * x[0] + a010 * x[1] + a001 * x[2] + + a200 * xx + a020 * yy + a002 * zz + + a110 * xy + a011 * yz + a101 * zx + + a120 * x[0] * yy + a012 * x[1] * zz + a201 * x[2] * xx + + a210 * xx * x[1] + a021 * yy * x[2] + a102 * zz * x[0] + + a220 * xx * yy + a022 * yy * zz + a202 * zz * xx + + a112 * xy * x[2] + a211 * yz * x[0] + a121 * zx * x[1] + + a122 * x[0] * yy * zz + a212 * xx * x[1] * zz + a221 * xx * yy * x[2]; + return vPeak; + } + } + } + +} + +#endif//_SHT_XCORR_H_ diff --git a/include/sht/square_sht.hpp b/include/sht/square_sht.hpp new file mode 100644 index 0000000..0ae0e04 --- /dev/null +++ b/include/sht/square_sht.hpp @@ -0,0 +1,1164 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _SQUARE_SHT_H_ +#define _SQUARE_SHT_H_ + +#include +#include +#include + +#include "util/fft.hpp"//for ffts in DiscreteSHT + +//@brief: functions related to square <--> hemisphere mappings +//mappings must be the following criteria: +// -rings of constant latitude points on the sphere map to square rings +// -rings are symmtric across the equator +// -points within a ring a equally spaced +// -the sidelength of the square grid is odd +// -the number of points in the rings (from pole -> equator) is 1, 8, 16, 24, 32, ... +// -the first point in each ring is at an azimuthal angle of 0 +// -the exterior of each square is the same and lies on the equator + +namespace emsphinx { + + namespace square { + + enum class Layout {//types of square grids + Lambert ,//equal area projection + Legendre //equal lattitude rings at legendre roots (+ extra points at poles) for improved numerical stability + }; + + //helper class to perform discrete spherical harmonic transformations with the square lambert grid + //@reference: Reinecke , M. (2011). Libpsht–algorithms for efficient spherical harmonic transforms. Astronomy & Astrophysics, 526, A108. + //@reference: Schaeffer, N. (2013). Efficient spherical harmonic transforms aimed at pseudospectral numerical simulations. Geochemistry, Geophysics, Geosystems, 14(3), 751-758. + //@note: grid requirements: + // -pixels are arranged on N_\phi \approx \sqrt{N_{pix}} iso-latitude rings w/ colatitude of ring y = \theta_y + // -within a ring pixels are equidistance in azimuthal angle and have identical weight (solid angle) w_y + // -the number of pixels in a given ring N_{\phi,y} can vary + // -the first pixel in a ring is at azimuthal angle \phi_{0,y} + // the square lambert grid satisfies these requirements: + // -\sqrt{ 2.5 * N_{pix} } rings for side length 3, rapidly approaching \sqrt{2 * N_{pix} } rings (within 1% at side length 9) + // -all grid points cover the same solid angle -> w_y = 1 / rings + // -\phi_{0,y} = 0 for odd side lengths (must be calculated for even side lengths) + // -for the reverse transformation (synthesis) all N_{\phi,y} are even + //@note: optimizations implemented (see Schaeffer for details): + // -use of real values FFT -> factor of 2 savings on FFT + // -mirror (conjugate) symmetry of spherical harmonics -> factor of 2 on direct summations [only possible for ring positions that are symmetric across the equator] + // -polar optimization (implicitely via equal area grid) + //@note: I've opted for on the fly (vs precomputed calculations) since the single thread performance was very similar and on the fly has much lower memory overhead + //@note: single thread performance is comparable to SHTns compiled without SIMD enabled for the same number of rings, but non legendre root rings -> half the bandwidth + //@note: complexity is ~n^2.7 for reasonable bandwidths (should probably be something like log(n)*n^2) + template + class DiscreteSHT { + struct Constants;//helper struct to hold read only constants + const std::shared_ptr shtLut ;//read only values (can be shared across threads) + fft::vector< std::complex > cWrk1, cWrk2;//complex working arrays for a single ring + fft::vector< Real > rWrk1, rWrk2;//real working array for a single ring + + public: + //@brief : construct a spherical harmonic transformer for a given side length and max bandwidth + //@param dim: side length of square projection to perform transformations for + //@param mBw: maximum bandwidth to compute + //@param lay: type of square projection to use + DiscreteSHT(const size_t dim, const size_t mBw, const Layout lay); + + //@brief : construct a spherical harmonic transformer for a given side length and max bandwidth + //@param dim: side length of square projection to perform transformations for + //@param mBw: maximum bandwidth to compute + //@param cLt: cosines or ring latitudes (northern hemisphere only, mirrored across equator) + //@param leg: true/false if the ring latitudes in cLt are legendre roots (+poles for odd sizes) + DiscreteSHT(const size_t dim, const size_t mBw, Real const * const cLt, const bool leg); + + //@brief : convince method for constructing a square legendre transformer for the specified bandwidth + //@param dim: side length of square legendre projection to perform transformations for + //@return : transformer for specified sidelength, available bandwidth can be determined with maxBw() + static DiscreteSHT Legendre(const size_t dim) {return DiscreteSHT(dim, dim - 2, Layout::Legendre);}//poles aren't used + + //@brief : convince method for constructing a square lambert transformer for the specified bandwidth + //@param dim: side length of square lambert projection to perform transformations for + //@return : transformer for specified sidelength, available bandwidth can be determined with maxBw() + static DiscreteSHT Lambert(const size_t dim) {return DiscreteSHT(dim, (dim-1)/2, Layout::Lambert);}//half number of rings, equator has double cover + + //@brief : compute spherical harmonic coefficients from a spherical function (forward transformation) + //@param nh : value of function to analyze at each grid point for the north hemisphere (row major order) + //@param sh : value of function to analyze at each grid point for the south hemisphere (row major order) + //@param alm: location to write alm values bw * bw with (m,l) stored: (0,0), (0,1), (0,2), ..., (0,bw-1), (1,0), (1,1), (1,2), ..., (bw-1,0), (bw-1,1), (bw-1,bw-1) + //@param bw : maximum bandwidth to compute (must be <= mBw argument from construction, 0 to use mBw from construction) + //@param stM: stride between sequential m values of the same l in alm, i.e. a^l_m is at alm[stM * m + l], 0 to use bw + //@note : a^l_{-m} = std::conj((m % 2 == 0) ? a^l_{-m} : -a^l_{-m}) if they are needed + void analyze(Real const * const nh, Real const * const sh, std::complex * const alm, const size_t bw, const size_t stM); + + //@brief : compute spherical harmonic coefficients from a spherical function (forward transformation) + //@param nh : value of function to analyze at each grid point for the north hemisphere (row major order) + //@param sh : value of function to analyze at each grid point for the south hemisphere (row major order) + //@param alm: location to write alm values bw * bw with (m,l) stored: (0,0), (0,1), (0,2), ..., (0,bw-1), (1,0), (1,1), (1,2), ..., (bw-1,0), (bw-1,1), (bw-1,bw-1) + //@note : a^l_{-m} = std::conj((m % 2 == 0) ? a^l_{-m} : -a^l_{-m}) if they are needed + void analyze(Real const * const nh, Real const * const sh, std::complex * const alm) {analyze(nh, sh, alm, 0, 0);} + + //@brief : compute spherical harmonic coefficients from a spherical function (forward transformation) + //@param pts: value of function to analyze at each grid point (row major order, northern followed by southern hemisphere) + //@param alm: location to write alm values bw * bw with (m,l) stored: (0,0), (0,1), (0,2), ..., (0,bw-1), (1,0), (1,1), (1,2), ..., (bw-1,0), (bw-1,1), (bw-1,bw-1) + //@param bw : maximum bandwidth to compute (must be <= mBw argument from construction, 0 to use mBw from construction) + //@param stM: stride between sequential m values of the same l in alm, i.e. a^l_m is at alm[stM * m + l], 0 to use bw + //@note : a^l_{-m} = std::conj((m % 2 == 0) ? a^l_{-m} : -a^l_{-m}) if they are needed + void analyze(Real const * const pts, std::complex * const alm, const size_t bw, const size_t stM) {analyze(pts, pts + shtLut->dim * shtLut->dim, alm, bw, stM);} + + //@brief : compute spherical harmonic coefficients from a spherical function (forward transformation) + //@param pts: value of function to analyze at each grid point (row major order, northern followed by southern hemisphere) + //@param alm: location to write alm values bw * bw with (m,l) stored: (0,0), (0,1), (0,2), ..., (0,bw-1), (1,0), (1,1), (1,2), ..., (bw-1,0), (bw-1,1), (bw-1,bw-1) + //@note : a^l_{-m} = std::conj((m % 2 == 0) ? a^l_{-m} : -a^l_{-m}) if they are needed + void analyze(Real const * const pts, std::complex * const alm) {analyze(pts, alm, 0, 0);} + + //@brief : compute spherical function from spherical harmonic coefficients (inverse transformation) + //@param alm: alm values bw * bw with (m,l) stored: (0,0), (0,1), (0,2), ..., (0,bw-1), (1,0), (1,1), (1,2), ..., (bw-1,0), (bw-1,1), (bw-1,bw-1) + //@param nh : location to write north hemisphere of spherical function (row major order) + //@param sh : location to write north hemisphere of spherical function (row major order) + //@param bw : maximum bandwidth to use in synthesis (must be <= mBw argument from construction, 0 to use mBw from construction) + //@param stM: stride between sequential m values of the same l in alm, i.e. a^l_m is at alm[stM * m + l], 0 to use bw + //@note : only non-negative m values are used since SHT of real function is conjugate symmetric + void synthesize(std::complex const * const alm, Real * const nh, Real * const sh, const size_t bw, const size_t stM); + + //@brief : compute spherical function from spherical harmonic coefficients (inverse transformation) + //@param alm: alm values bw * bw with (m,l) stored: (0,0), (0,1), (0,2), ..., (0,bw-1), (1,0), (1,1), (1,2), ..., (bw-1,0), (bw-1,1), (bw-1,bw-1) + //@param nh : location to write north hemisphere of spherical function (row major order) + //@param sh : location to write north hemisphere of spherical function (row major order) + //@note : only non-negative m values are used since SHT of real function is conjugate symmetric + void synthesize(std::complex const * const alm, Real * const nh, Real * const sh) {synthesize(alm, nh, sh, 0, 0);} + + //@brief : compute spherical function from spherical harmonic coefficients (inverse transformation) + //@param alm: alm values bw * bw with (m,l) stored: (0,0), (0,1), (0,2), ..., (0,bw-1), (1,0), (1,1), (1,2), ..., (bw-1,0), (bw-1,1), (bw-1,bw-1) + //@param pts: location to write spherical function (row major order, northern followed by southern hemisphere) + //@param bw : maximum bandwidth to use in synthesis (must be <= mBw argument from construction, 0 to use mBw from construction) + //@param stM: stride between sequential m values of the same l in alm, i.e. a^l_m is at alm[stM * m + l], 0 to use bw + //@note : only non-negative m values are used since SHT of real function is conjugate symmetric + void synthesize(std::complex const * const alm, Real * const pts, const size_t bw, const size_t stM) {synthesize(alm, pts, pts + shtLut->dim * shtLut->dim, bw, stM);} + + //@brief : compute spherical function from spherical harmonic coefficients (inverse transformation) + //@param alm: alm values bw * bw with (m,l) stored: (0,0), (0,1), (0,2), ..., (0,bw-1), (1,0), (1,1), (1,2), ..., (bw-1,0), (bw-1,1), (bw-1,bw-1) + //@param pts: location to write spherical function (row major order, northern followed by southern hemisphere) + //@note : only non-negative m values are used since SHT of real function is conjugate symmetric + void synthesize(std::complex const * const alm, Real * const pts) {synthesize(alm, pts, 0, 0);} + + //@brief : get the maximum bandwidth that can be computed with analyze / used by synthesize + //@return: maximum bandwidth + size_t maxBw() const; + + //@brief : get the side length of input signals for analyze / output signals from synthesize + //@return: side length of square projection + size_t dim() const; + }; + + //these functions are specifically for the area preserving square <--> sphere mapping + //@reference: Roşca, D. (2010). New uniform grids on the sphere. Astronomy & Astrophysics, 520, A63. + namespace lambert { + //@brief: square lambert projection from unit hemisphere to unit square + //@param x: x coordinate on unit sphere + //@param y: y coordinate on unit sphere + //@param z: z coordinate on unit sphere + //@param X: location to write x coordinate in unit square (0,1) + //@param Y: location to write y coordinate in unit square (0,1) + template void sphereToSquare(Real const& x, Real const& y, Real const& z, Real& X, Real& Y); + + //@brief: square lambert projection from unit square to unit hemisphere + //@param X: x coordinate in unit square (0,1) + //@param Y: y coordinate in unit square (0,1) + //@param x: location to write x coordinate on unit sphere + //@param y: location to write y coordinate on unit sphere + //@param z: location to write z coordinate on unit sphere + template void squareToSphere(Real const& X, Real const& Y, Real& x, Real& y, Real& z); + + //@brief : compute cosine of the latitude of each ring in the northern hemisphere (including equator) + //@note : southern hemisphere cosines can be computed by symmetry with cos(lat[i]) = -cos(lat[# rings - i]) + //@param dim: side length of square lambert projection + //@param lat: location to write cos(ring latitudes) + template void cosLats(const size_t dim, Real * const lat); + + //@brief : compute the real space coordinates of unprojected points in a square lambert grid (north hemisphere only) + //@param dim: side length of square legendre grid (must be odd) + //@param xyz: location to write real space coordiantes (dim * dim * 3) + template void normals(const size_t dim, Real * const xyz); + + //@brief : compute the solid angle correction of each grid point in the square lambert projection + //@param dim: side length of square lambert projection to compute solid angles for + //@param omg: location to write grid point solid angles (northern hemisphere only, row major order) + //@reference: Mazonka, Oleg. "Solid angle of conical surfaces, polyhedral cones, and intersecting spherical caps." arXiv preprint arXiv:1205.1396 (2012). + template void solidAngles(const size_t dim, Real * const omg); + } + + //these functions are specifically for the grid with rings at legendre polynomial roots + namespace legendre { + //@brief : compute cosine of the latitude of each ring in the northern hemisphere (including equator) + //@note : southern hemisphere cosines can be computed by symmetry with cos(lat[i]) = -cos(lat[# rings - i]) + //@param dim: side length of square legendre projection + //@param lat: location to write cos(ring latitudes) + //@reference: Barth, W., Martin, R. S., & Wilkinson, J. H. (1967). Calculation of the eigenvalues of a symmetric tridiagonal matrix by the method of bisection. Numerische Mathematik, 9(5), 386-393. + //@method : legendre roots are calculated as zeros of a symmetric tridiagonal matrix as described here - https://math.stackexchange.com/questions/12160/roots-of-legendre-polynomial/12209#12209 + template void roots(const size_t dim, Real * const lat); + + //@brief : compute the real space coordinates of unprojected points in a square legendre grid (north hemisphere only) + //@param dim: side length of square legendre grid (must be odd) + //@param xyz: location to write real space coordiantes (dim * dim * 3) + template void normals(const size_t dim, Real * const xyz); + + //@brief : compute the 4 bounding indices of for a given unit direction in a the north hemisphere of square legendre grid + //@param dim : side length of square legendre grid (must be odd) + //@param zLat: z coordinates of each ring in the projection (e.g. from cosLats) + //@param n : direction to get bounding indices for + //@param inds: location to write bounding indices + template void boundingInds(const size_t dim, Real const * const zLat, Real const * const n, size_t * const inds); + } + + //@brief : extract a single ring from a row major pattern + //@param dim : side length of square in pixels + //@param ring: ring number to copy (0 for north pole, (dim-1)/2 for equator) + //@param ptr : pointer to start of hemisphere to copy from (dim * dim array in row major order) + //@param buff: location to write extracted ring + //@return : number of values copied + template size_t readRing(const size_t dim, const size_t ring, T const * const ptr, T * const buff); + + //@brief : write a single ring into a row major pattern + //@param dim : side length of square in pixels + //@param ring: ring number to write (0 for north pole, (dim-1)/2 for equator) + //@param ptr : pointer to start of hemisphere to write to (dim * dim array in row major order) + //@param buff: location to read ring from + //@return : number of values copied + template size_t writeRing(const size_t dim, const size_t ring, T * const ptr, T const * const buff); + + //@brief : compute quadrature weights for rings (w_y in equation 10 of Reinecke) + //@param dim: side length of square lambert projection to compute weights for + //@param lat: cosines of ring latitudes (symmetric across equator) + //@param wgt: location to write weights for each row + //@param skp: ring to exclude from weights (e.g. skip = 0 will exclude the poles) + //@reference: https://doi.org/10.1111/j.1365-246X.1994.tb03995.x + template void computeWeightsSkip(const size_t dim, Real const * const lat, Real * const wgt, const size_t skp); + + //@brief : compute the cosines of ring latitudes for a given square grid + //@param dim : side length of square projection to compute ring latitudes for + //@param type: type of square projection to compute latitudes for + //@return : cosine(latitudes) from north pole -> equator + template typename std::vector cosLats(const size_t dim, const Layout type); + + //@brief : compute the normals of the northern hemisphere for a given square grid + //@param dim : side length of square projection to compute ring latitudes for + //@param type: type of square projection to compute latitudes for + //@return : {x,y,z} normals for north pole + template typename std::vector normals(const size_t dim, const Layout type); + + //@brief : compute the solid angle correction of each ring for a given square grid + //@param dim : side length of square lambert projection to compute solid angles for + //@param type: type of square projection to compute latitudes for + //@return : solide angle correction from north pole -> equator (actual pixel size / average pixel size) + template typename std::vector solidAngles(const size_t dim, const Layout type); + + //@brief : convert from a vecotrized index to a ring number + //@param dim: side length of square lambert projection to compute index for + //@param idx: vectorized index (y * dim + x) to compute ring number for [must be in north hemisphere i.e. < dim * dim] + //@return : ring number (e.g. for indexing into cosLats) + size_t ringNum(const size_t dim, const size_t idx); + } + +} + +//////////////////////////////////////////////////////////////////////// +// Implementations // +//////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include "constants.hpp"//for computeWeights +#include "util/linalg.hpp"//for computeWeights + +namespace emsphinx { + + namespace square { + //helper struct to hold read only constants needed by DiscreteSHT (for sharing across threads) + template + struct DiscreteSHT::Constants { + //these members could be shared across threads since they should be read only after construction + const size_t dim ;//side length of square lambert projection + const size_t Nt ;//number of pairs of equal latitude rings [(dim+1) / 2] + const size_t maxL ;//maximum bandwidth of square lambert projection (must be < Nt for arbitrary rings (Nt*2 for legendre rings)) + const size_t Nw ;//number of different types of weights [(dim-2) / 8 + 1] + std::vector wy ;//weighting factor for each ring [Nt * Nw] + std::vector cosTy;//cosine of latitude of each ring [Nt] + std::vector amn ;//precomputed a^m_n values for on the fly ylm calculation [maxL^2] + std::vector bmn ;//precomputed b^m_n values for on the fly ylm calculation [maxL^2] + std::vector< std::shared_ptr< fft::RealFFT > > ffts;//fft plans [Nt] + + //@brief : construct a set of constants for a given side length and max bandwidth + //@param d: side length of square lambert projection to perform transformations for + //@param l: maximum bandwidth to compute + //@param c: cosines or ring latitudes (northern hemisphere only, mirrored across equator) + //@param b: true/false if the ring latitudes in c are legendre roots (+poles for odd sizes) + Constants(const size_t d, const size_t l, Real const * const c, const bool b); + }; + + //@brief : construct a set of constants for a given side length and max bandwidth + //@param d: side length of square lambert projection to perform transformations for + //@param l: maximum bandwidth to compute (exclusive) + //@param c: cosines or ring latitudes (northern hemisphere only, mirrored across equator) + //@param b: true/false if the ring latitudes in c are legendre roots (+poles for odd sizes) + template + DiscreteSHT::Constants::Constants(const size_t d, const size_t l, Real const * const c, const bool b) : dim(d), Nt( (dim+1) / 2 ), maxL(l), Nw((dim-2) / 4 + 1), wy(Nt * Nw), cosTy(c, c + Nt), amn(maxL*maxL), bmn(maxL*maxL), ffts(Nt) { + //sanity check bandwidth and dimensions + if(dim < 3) throw std::domain_error("square lambert side length must be at least 3"); + if(dim % 2 == 0) throw std::domain_error("only odd side lengths are supported"); + if(b) { + const size_t limit = 2 * (Nt - 1) + (1 - dim % 2); + if(maxL >= limit) throw std::domain_error("maximum bandwidth is side # rings - 1 (for legendre root latitudes)"); + } else { + if(maxL >= Nt) throw std::domain_error("maximum bandwidth is side length / 2"); + } + + //compute amn and bmn values + const Real k4p = Real(1) / (emsphinx::Constants::pi * 4);//1 / (4 pi): constant for a^m_m calculation + Real kamm = 1;//\Pi_k=1^|m| \frac{2k+1}{2k} for m = 0: for a^m_m calculation + for(size_t m = 0; m < maxL; m++) { + //first compute a^m_m + amn[m*maxL + m] = std::sqrt(kamm * k4p);//recursively compute a^m_m (Schaeffer equation 16) + kamm *= Real(2*m+3) / (2*m+2);//update recursion for \Pi_k=1^|m| \frac{2k+1}{2k} + if(m+1 == maxL) break; + + //now compute a^m_{m+1} + const size_t m2 = m * m;//we'll need this value a bunch of times + size_t n12 = m2;//(n-1)^2 for n = m+1 + size_t n2 = (m+1) * (m+1);//n^2 for n = m+1 + size_t n2m2 = n2 - m2;//n^2 - m^2 for n = m+1 + Real n12m2 = 0;//(n-1)^2 - m^2 for n = m+1 (not actually needed here but will be for recursion) + amn[m*maxL + m+1] = std::sqrt( Real(4 * n2 - 1) / n2m2 );//a^m_n for n = m+1 (Schaeffer equation 17) + + //now compute remaining a^m_n and b^m_n values + for(size_t n = m+2; n < maxL; n++) {//l is more commonly used but I'll stick with paper's notation + n12 = n2;//use previously computed value of n^2 for (n-1)^2 + n12m2 = (Real)n2m2;//use previously computed values of n^2 - m^2 for (n-1)^2 - m^2 + n2 = n * n;//compute n^2 + n2m2 = n2 - m2;//compute n^2 - m^2 + amn[m*maxL + n] = std::sqrt( Real(4 * n2 - 1) / n2m2 );//a^m_n (Schaeffer equation 17) + bmn[m*maxL + n] = std::sqrt( ( Real(2*n+1) / (2*n-3) ) * ( n12m2 / n2m2 ) );//b^m_n (Schaeffer equation 18) + } + } + + //compute ring weights (this is the most expensive step for larger sizes since the scalling is ~dim^4) + if(b) { + computeWeightsSkip(dim, cosTy.data(), wy.data(), 0);//skip poles + for(size_t i = 1; i < Nw; i++) std::copy(wy.begin(), wy.begin() + Nt, wy.begin() + Nt * i); + } else { + for(size_t i = 0; i < Nw; i++) computeWeightsSkip(dim, cosTy.data(), wy.data() + i * Nt, i); + } + + //build fft calculators + for(size_t y = 0; y < Nt; y++) ffts[y] = std::make_shared< fft::RealFFT >(std::max(1, 8 * y), fft::flag::Plan::Patient);//we'll be doing many transforms, take the time to find a fast path + } + + //@brief : construct a spherical harmonic transformer for a given side length and max bandwidth + //@param dim: side length of square lambert projection to perform transformations for + //@param mBw: maximum bandwidth to compute + //@param lay: type of square projection to use + template + DiscreteSHT::DiscreteSHT(const size_t dim, const size_t mBw, const Layout lay) : DiscreteSHT(dim, mBw, cosLats(dim, lay).data(), lay == Layout::Legendre) {} + + //@brief : construct a spherical harmonic transformer for a given side length and max bandwidth + //@param dim: side length of square lambert projection to perform transformations for + //@param mBw: maximum bandwidth to compute (exclusive) + //@param cLt: cosines or ring latitudes (northern hemisphere only, mirrored across equator) + //@param leg: true/false if the ring latitudes in cLt are legendre roots (+poles for odd sizes) + template + DiscreteSHT::DiscreteSHT(const size_t dim, const size_t mBw, Real const * const cLt, const bool leg) : + shtLut(std::make_shared(dim, mBw, cLt, leg)), + cWrk1 (std::max(1, 4 * (dim-1))), + cWrk2 (std::max(1, 4 * (dim-1))), + rWrk1 (std::max(1, 4 * (dim-1))), + rWrk2 (std::max(1, 4 * (dim-1))) {} + + //@brief : compute spherical harmonic coefficients from a spherical function (forward transformation) + //@param nh : value of function to analyze at each grid point for the north hemisphere (row major order) + //@param sh : value of function to analyze at each grid point for the south hemisphere (row major order) + //@param alm: location to write alm values bw * bw with (m,l) stored: (0,0), (0,1), (0,2), ..., (0,bw-1), (1,0), (1,1), (1,2), ..., (bw-1,0), (bw-1,1), (bw-1,bw-1) + //@param bw : maximum bandwidth to compute (must be <= mBw argument from construction, 0 to use mBw from construction) + //@param stM: stride between sequential m values of the same l in alm, i.e. a^l_m is at alm[stM * m + l], 0 to use bw + //@note : a^l_{-m} = std::conj((m % 2 == 0) ? a^l_{-m} : -a^l_{-m}) if they are needed + template + void DiscreteSHT::analyze(Real const * const nh, Real const * const sh, std::complex * const alm, const size_t bw, const size_t stM) { + //parse arguments, sanity check, and initialize output + const size_t maxL = bw == 0 ? shtLut->maxL : bw;//what is the maximum bandwidth we need to compute + if(maxL > shtLut->maxL) throw std::runtime_error("maximum bandwidth must be <= maximum from DiscreteSHT construction"); + const size_t stride = stM == 0 ? maxL : stM;//what is the stride of the output array + if(stride < maxL) throw std::runtime_error("output array isn't big enough to store all coefficients"); + std::fill(alm, alm + stride * maxL, std::complex(0));//fill SHT with 0 + + //compute SHT ring at a time + for(size_t y = 0; y < shtLut->Nt; y++) {//loop over rings + //copy current rings and compute G_{m,y} by fft (Reinecke equation 10) leveraging real symmetry + const size_t Npy = std::max(1, 8 * y);//get number of points in this ring + const size_t fftN = Npy / 2 + 1;//number of fft points: half from real -> conjugate symmetric, this includes problematic points (imag == 0 from conmjugate symmetry) + readRing(shtLut->dim, y, nh, rWrk1.data());//copy current ring from row major order (north hemisphere) + readRing(shtLut->dim, y, sh, rWrk2.data());//copy current ring from row major order (south hemisphere) + shtLut->ffts[y]->forward(rWrk1.data(), cWrk1.data());//do ffts into complex working arrays (north hemisphere) + shtLut->ffts[y]->forward(rWrk2.data(), cWrk2.data());//do ffts into complex working arrays (south hemisphere) + + //compute G_{m,y} +/- G_{m,Nt-1-y} since they are multiplied by symmetric values + const size_t mLim = std::min(maxL, fftN);//anything after l+1 isn't needed and anything after fftN is 0 + for(size_t m = 0; m < mLim; m++) { + //get ring weight w_y + //weights excluding only the closest problematic ring (missing complex value due to real even dft) are most stable + accurate + //negate odd m values to correct for sign error in legendre polynomial calculation + const Real wy = shtLut->wy[size_t(m/4) * shtLut->Nt + y] * (m % 2 == 1 ? -1 : 1);//mod 4 from rings having 8y points + real symmetry of dft + + //combine northern / southern hemisphere rings to take advantages of spherical harmonic symmetry + const std::complex nPt = cWrk1[m] * wy;//northern hemisphere point + const std::complex sPt = cWrk2[m] * wy;//southern hemisphere point + cWrk1[m] = (nPt + sPt) * Real(0.5);//for even l+m (harmonics are symmetric across equator) + cWrk2[m] = (nPt - sPt) * Real(0.5);//for odd l+m (harmonics are antisymmetric across equator) + } + + //calculate seed values for on the fly legendre polynomial calculation + const Real x = shtLut->cosTy[y];//cosine of ring latitude + const Real r1x2 = std::sqrt(Real(1) - x * x);// (1-x)^\frac{1}{2}: constant for P^m_m calculation + Real kpmm = 1;//(1 - x^2) ^ \frac{|m|}{2} for m = 0: for P^m_m calculation + + //accumulate a_{l,m} via direct summation (Reinecke equation 9) via on the fly summation + Real const * amn = shtLut->amn.data(); + Real const * bmn = shtLut->bmn.data(); + std::complex * plm = alm; + for(int m = 0; m < mLim; m++) { + //get weighted values from fft + const std::complex& gmyS = cWrk1[m];//for symmetric modes + const std::complex& gmyA = cWrk2[m];//for antisymmetric modes + + //first compute P^m_m + Real pmn2 = amn[m] * kpmm;//recursively compute P^m_m (Schaeffer equation 13) + kpmm *= r1x2;//update recursion for (1 - x^2) ^ \frac{|m|}{2} + plm[m] += gmyS * pmn2; + if(m+1 == maxL) break;//P^m_{m+1} doesn't exist + + //now compute P^m_{m+1} + Real pmn1 = amn[m+1] * x * pmn2;//P^m_n for n = m+1 (Schaeffer equation 14) + plm[m+1] += gmyA * pmn1; + + //now compute remaining P^m_n values + for(int n = m+2; n < maxL; n++) {//l is more commonly used but I'll stick with paper's notation + Real pmn = amn[n] * x * pmn1 - bmn[n] * pmn2;//P^m_n (Schaeffer equation 15) + pmn2 = pmn1;//push back pmn values in recursion + pmn1 = pmn; //push back pmn values in recursion + plm[n] += ((n+m) % 2 == 0 ? gmyS : gmyA) * pmn; + } + + //increment pointers to next m + amn += shtLut->maxL; + bmn += shtLut->maxL; + plm += stride; + } + } + } + + //@brief : compute spherical function from spherical harmonic coefficients (inverse transformation) + //@param alm: alm values bw * bw with (m,l) stored: (0,0), (0,1), (0,2), ..., (0,bw-1), (1,0), (1,1), (1,2), ..., (bw-1,0), (bw-1,1), (bw-1,bw-1) + //@param nh : location to write north hemisphere of spherical function (row major order) + //@param sh : location to write north hemisphere of spherical function (row major order) + //@param bw : maximum bandwidth to use in synthesis (must be <= mBw argument from construction, 0 to use mBw from construction) + //@param stM: stride between sequential m values of the same l in alm, i.e. a^l_m is at alm[stM * m + l], 0 to use bw + //@note : only non-negative m values are used since SHT of real function is conjugate symmetric + template + void DiscreteSHT::synthesize(std::complex const * const alm, Real * const nh, Real * const sh, const size_t bw, const size_t stM) { + //parse arguments and sanity check + const size_t maxL = bw == 0 ? shtLut->maxL : bw;//what is the maximum bandwidth we need to compute + if(maxL > shtLut->maxL) throw std::runtime_error("maximum bandwidth must be <= maximum from DiscreteSHT construction"); + const size_t stride = stM == 0 ? maxL : stM;//what is the stride of the output array + if(stride < maxL) throw std::runtime_error("input array isn't big enough to store all coefficients"); + + //compute inverse SHT ring at a time + for(size_t y = 0; y < shtLut->Nt; y++) {//loop over rings + //compute F_{m,y} +- F_{m,Nt-1-y} leveraging symmetry of spherical harmonics across equator + const size_t Npy = std::max(1, 8 * y);//get number of points in this ring + const size_t fftN = Npy / 2 + 1;//number of fft points: half from real -> conjugate symmetric, this includes problematic points (imag == 0 from conmjugate symmetry) + + //calculate seed values for on the fly legendre polynomial calculation + const Real x = shtLut->cosTy[y];//cosine of ring latitude + const Real r1x2 = std::sqrt(Real(1) - x * x);// (1-x)^\frac{1}{2}: constant for P^m_m calculation + Real kpmm = 1;//(1 - x^2) ^ \frac{|m|}{2} for m = 0: for P^m_m calculation + + //accumulate F_{m,y} +- F_{m,Nt-1-y} by direct summation (Reinecke equation 8) + Real const * amn = shtLut->amn.data(); + Real const * bmn = shtLut->bmn.data(); + std::complex const * plm = alm; + + const size_t mLim = std::min(maxL, fftN);//anything after l+1 isn't needed and anything after fftN is 0 + for(size_t m = 0; m < mLim; m++) { + //get reference to even and odd sums and zero + std::complex& fmyS = cWrk1[m];//for symmetric modes + std::complex& fmyA = cWrk2[m];//for antisymmetric modes + fmyS = fmyA = Real(0); + + //first compute P^m_m + Real pmn2 = amn[m] * kpmm;//recursively compute P^m_m (Schaeffer equation 13) + kpmm *= r1x2;//update recursion for (1 - x^2) ^ \frac{|m|}{2} + fmyS += plm[m] * pmn2; + if(m+1 == maxL) break;//P^m_{m+1} doesn't exist + + //now compute P^m_{m+1} + Real pmn1 = amn[m+1] * x * pmn2;//P^m_n for n = m+1 (Schaeffer equation 14) + fmyA += plm[m+1] * pmn1; + + //now compute remaining P^m_n values + for(size_t n = m+2; n < maxL; n++) {//l is more commonly used but I'll stick with paper's notation + Real pmn = amn[n] * x * pmn1 - bmn[n] * pmn2;//P^m_n (Schaeffer equation 15) + pmn2 = pmn1;//push back pmn values in recursion + pmn1 = pmn; //push back pmn values in recursion + ((n+m) % 2 == 0 ? fmyS : fmyA) += plm[n] * pmn; + } + + //increment pointers to next m + amn += shtLut->maxL; + bmn += shtLut->maxL; + plm += stride; + } + + //now convert from F_{m,y} +- F_{m,Nt-1-y} -> F_{m,y} and F_{m,Nt-1-y} + for(size_t m = 0; m < mLim; m++) { + //combine northern / southern hemisphere rings to take advantages of spherical harmonic symmetry + //negate odd m values to correct for sign error in legendre polynomial calculation + const std::complex sigma = cWrk1[m] * Real(m % 2 == 1 ? -1 : 1);//F_{m,y} + F_{m,Nt-1-y} + const std::complex delta = cWrk2[m] * Real(m % 2 == 1 ? -1 : 1);//F_{m,y} - F_{m,Nt-1-y} + cWrk1[m] = sigma + delta;//northern hemisphere point + cWrk2[m] = sigma - delta;//southern hemisphere point + } + + //fill in remaining fft with 0 (we don't have data for frequencies this high) + if(fftN >= maxL) { + std::fill(cWrk1.begin() + maxL, cWrk1.begin() + fftN + 1, std::complex(0)); + std::fill(cWrk2.begin() + maxL, cWrk2.begin() + fftN + 1, std::complex(0)); + } + + //do the inverse ffts of F_{m,y} and F_{m,Nt-1-y} (Reinecke equation 7) and copy to output + shtLut->ffts[y]->inverse(cWrk1.data(), rWrk1.data());//do ffts into real working arrays (north hemisphere) + shtLut->ffts[y]->inverse(cWrk2.data(), rWrk2.data());//do ffts into real working arrays (south hemisphere) + writeRing(shtLut->dim, y, nh, rWrk1.data());//copy current ring to row major order (north hemisphere) + writeRing(shtLut->dim, y, sh, rWrk2.data());//copy current ring to row major order (south hemisphere) + } + } + + //@brief : get the maximum bandwidth that can be computed with analyze / used by synthesize + //@return: maximum bandwidth + template + size_t DiscreteSHT::maxBw() const {return shtLut->maxL;} + + //@brief : get the side length of input signals for analyze / output signals from synthesize + //@return: side length of square + template + size_t DiscreteSHT::dim() const {return shtLut->dim;} + + namespace lambert { + //@brief: square lambert projection from unit hemisphere to unit square + //@param x: x coordinate on unit sphere + //@param y: y coordinate on unit sphere + //@param z: z coordinate on unit sphere + //@param X: x coordinate in unit square (0,1) + //@param Y: y coordinate in unit square (0,1) + template + void sphereToSquare(Real const& x, Real const& y, Real const& z, Real& X, Real& Y) { + static const Real kPi_4 = Real(0.7853981633974483096156608458199);//pi/4 + const Real fZ = std::fabs(z); + if(fZ == 1.0) { + X = Y = Real(0.5); + } else if(std::abs(y) <= std::abs(x)) { + X = std::copysign(std::sqrt(Real(1) - fZ), x) * Real(0.5);//[-0.5, 0.5] + Y = X * std::atan(y / x) / kPi_4 + Real(0.5);//[0, 1] + X += Real(0.5);//[0, 1] + } else { + Y = std::copysign(std::sqrt(Real(1) - fZ), y) * Real(0.5);//[-0.5, 0.5] + X = Y * std::atan(x / y) / kPi_4 + Real(0.5);//[0, 1] + Y += Real(0.5);//[0, 1] + } + } + + //@brief: square lambert projection from unit square to unit hemisphere + //@param X: x coordinate in unit square (0,1) + //@param Y: y coordinate in unit square (0,1) + //@param x: location to write x coordinate on unit sphere + //@param y: location to write y coordinate on unit sphere + //@param z: location to write z coordinate on unit sphere + template + void squareToSphere(Real const& X, Real const& Y, Real& x, Real& y, Real& z) { + static const Real kPi_4 = Real(0.7853981633974483096156608458199);//pi/4 + const Real sX = Real(2) * X - 1;//[0,1] -> [-1, 1] + const Real sY = Real(2) * Y - 1;//[0,1] -> [-1, 1] + const Real aX = std::abs(sX); + const Real aY = std::abs(sY); + const Real vMax = std::max(aX, aY); + if(vMax <= std::numeric_limits::epsilon()) { + x = y = Real(0); + z = Real(1); + } else { + if(vMax > Real(1) + std::numeric_limits::epsilon()) throw std::runtime_error("point doesn't lie in square (0,0) -> (1,1)"); + if(aX <= aY) { + const Real q = sY * std::sqrt(Real(2) - sY * sY); + const Real qq = kPi_4 * sX / sY; + x = q * std::sin(qq); + y = q * std::cos(qq); + } else { + const Real q = sX * std::sqrt(Real(2) - sX * sX); + const Real qq = kPi_4 * sY / sX; + x = q * std::cos(qq); + y = q * std::sin(qq); + } + z = Real(1) - vMax * vMax; + const Real mag = std::sqrt(x*x + y*y + z*z); + x /= mag; y /= mag; z /= mag; + } + } + + //@brief : compute cosine of the latitude of each ring in the northern hemisphere (including equator) + //@note : southern hemisphere cosines can be computed by symmetry with cos(lat[i]) = -cos(lat[# rings - i]) + //@param dim: side length of square lambert projection + //@param lat: location to write cos(ring latitudes) + template + void cosLats(const size_t dim, Real * const lat) { + const size_t count = (dim + 1) / 2;//only need north hemisphere rings + const bool even = (0 == dim % 2);//even and odd are slightly different + const size_t denom = (dim - 1) * (dim - 1);//compute denominator of latitude cosines + size_t numer = denom - (even ? 1 : 0);//odd starts @ pole, even just off + size_t delta = even ? 8 : 4;//starting increment is similarly different + for(size_t i = 0; i < count; i++) {//loop over rings computing cosine of ring latitude (this is the same for even and odd) + lat[i] = Real(numer) / denom;//compute cos(latitude(ring i)) + numer -= delta;//update numerator + delta += 8;//update change to numerator + } + } + + //@brief : compute the real space coordinates of unprojected points in a square lambert grid (north hemisphere only) + //@param dim: side length of square legendre grid (must be odd) + //@param xyz: location to write real space coordiantes (dim * dim * 3) + template void normals(const size_t dim, Real * const xyz) { + //loop over northern hemisphere compute + for(size_t j = 0; j < dim; j++) { + const Real y = Real(j) / (dim - 1);//[0, 1] + for(size_t i = 0; i < dim; i++) { + const Real x = Real(i) / (dim - 1);//[0, 1] + Real * const n = xyz + 3 * (dim * j + i); + squareToSphere(x, y, n[0], n[1], n[2]); + } + } + } + + //@brief : compute the solid angle correction of each grid point in the square lambert projection + //@param dim: side length of square lambert projection to compute solid angles for + //@param omg: location to write grid point solid angles (northern hemisphere only, row major order) + //@reference: Mazonka, Oleg. "Solid angle of conical surfaces, polyhedral cones, and intersecting spherical caps." arXiv preprint arXiv:1205.1396 (2012). + template + void solidAngles(const size_t dim, Real * const omg) { + const bool even = 0 == dim % 2;//check if the grid has an odd or even side length + const size_t totalPixels = dim * dim * 2 - 4 * (dim - 1);//count total number of pixels in square lambert sphere + const Real invOmegaBar = Real(totalPixels) / (Real(M_PI) * 4);//determine the reciprocal of the average solid angle of pixels in the projection + const size_t mid = dim / 2;//first point on / across from the poles + const Real delta = Real(0.5) / (dim - 1);//spacing from center to corenrs of solid angle in square lambert space + for(size_t y = mid; y < dim; y++) {//loop over rows skipping first half (mirror plane at Y = 0) + const bool maxY = dim == y + 1;//check if we're on the equator + const Real Y = Real(y) / (dim - 1);//fractional position in unit square + const Real Ym = Y - delta;//compute bottom extent of pixel + const Real Yp = Y + (maxY ? 0 : delta);//compute top extent of pixel (without crossing equator) + for(size_t x = y; x < dim; x++) {//loop over columns skipping first half (mirror plane X = 0) and stopping at 45 degrees (mirror plane at X == Y) + const bool maxX = dim == x + 1;//check if we're on the equator + const Real X = Real(x) / (dim - 1);//fractional position in unit square + const Real Xm = X - delta;//compute left extent of pixel + const Real Xp = X + (maxX ? 0 : delta);//compute right extent of pixel (without crossing equator) + + //now compute the spherical coordinates of the corners of the pixels + Real s[4][3];//corners of square lambert projection (clockwise order) + squareToSphere(Xm, Ym, s[0][0], s[0][1], s[0][2]);//bottom left + squareToSphere(Xp, Ym, s[1][0], s[1][1], s[1][2]);//bottom right + squareToSphere(Xp, Yp, s[2][0], s[2][1], s[2][2]);//top right + squareToSphere(Xm, Yp, s[3][0], s[3][1], s[3][2]);//top left + + //compute solid angle of this pixel (see @reference equation 25 for details) + std::complex product = 1; + for(size_t j = 0; j < 4; j++) { + const size_t jm = (j + 3) % 4;//j-1 + const size_t jp = (j + 1) % 4;//j+1 + const Real aj = std::inner_product(s[jm], s[jm]+3, s[jp], Real(0)); + const Real bj = std::inner_product(s[jm], s[jm]+3, s[j ], Real(0)); + const Real cj = std::inner_product(s[j ], s[j ]+3, s[jp], Real(0));//==b[jp] + Real cross[3] = {//s[j] X s[jp] + s[j][1] * s[jp][2] - s[j][2] * s[jp][1], + s[j][2] * s[jp][0] - s[j][0] * s[jp][2], + s[j][0] * s[jp][1] - s[j][1] * s[jp][0] + }; + const Real dj = std::inner_product(s[jm], s[jm]+3, cross, Real(0)); + product *= std::complex(bj * cj - aj, dj); + } + + //compute the ratio of the actual pixel solid angle to the average pixel solid angle and mirror over X==Y plane + int factor = 1;//most pixels are completely in the north hemisphere + if(maxX) factor *= 2;//pixels on the right edge of the grid are half below the equator + if(maxY) factor *= 2;//pixels on the top edge of the grid are half below the equator + omg[dim * y + x] = omg[dim * x + y] = -std::atan2(product.imag(), product.real()) * factor * invOmegaBar;//area is arg(product) + } + + //now that the +x half of the row has been filled in copy to the -x half + std::reverse_copy(omg + dim * y + mid, omg + dim * y + dim, omg + dim * y); + } + + //now that the entire +y half of the grid has been filled in copy to the -y half + std::reverse_copy(omg + mid * dim, omg + dim * dim, omg); + } + } + + namespace legendre { + //@brief : compute cosine of the latitude of each ring in the northern hemisphere (including equator) + //@note : southern hemisphere cosines can be computed by symmetry with cos(lat[i]) = -cos(lat[# rings - i]) + //@param dim: side length of square legendre projection + //@param lat: location to write cos(ring latitudes) + //@reference: Barth, W., Martin, R. S., & Wilkinson, J. H. (1967). Calculation of the eigenvalues of a symmetric tridiagonal matrix by the method of bisection. Numerische Mathematik, 9(5), 386-393. + //@method : legendre roots are calculated as zeros of a symmetric tridiagonal matrix as described here - https://math.stackexchange.com/questions/12160/roots-of-legendre-polynomial/12209#12209 + template + void roots(const size_t dim, Real * const lat) { + //switch to variable names in paper for convenience + const size_t n = dim; + Real * const x = lat; + + //don't need to build diagonal (it is all zeros) + //build subdiagonal of legendre matrix: i / sqrt(4i^2-1) + std::vector b(n), beta(n); + for(size_t i = 1; i < n; i++) { + const Real den = Real(4*i*i-1); + b [i] = Real(i ) / std::sqrt(den);//subdiagonal + beta[i] = Real(i*i) / den ;//subdiagonal^2 + } + + size_t z = 0;//total iterations to calculate all values + const size_t limit = n * 32;//this should be enough for a 128 bit float (long double) + + const size_t m1 = n/2;//smallest eigen value to calculate (all larger values are calculated) + Real eps1 = std::numeric_limits::epsilon();//target precision + Real relfeh = std::numeric_limits::epsilon();//machine epsilon + + //reference pseudocode: calculation of xmin, xmax + beta[1-1] = b[1-1] = 0; + Real eps2 = relfeh; + if(eps1 <= 0) eps1 = eps2; + eps2 = eps1 / 2 + 7 * eps2;//maximum absolute error in eigenvalues + + //reference pseudocode: inner block + std::vector wu(m1+1, Real(0));//array to hold lower bounds (upper bounds are stored in x), fill lower bounds with 0 + Real x0 = 1;//upper bound on current eigenvalue (start with largest) + std::fill(x + 0, x + m1+1, Real(1));//fill upper bounds + if(1 == n%2) x[m1] = 0;//explicitly specify middle eigenvalue for odd case + //reference pseudocode: endi + for(int k = 0; k < m1; k++) {//loop over eigen values in reverse order calculating (largest -> smallest) + //get initial lower bound on eigen value k + Real xu = 0;//start with lower bound on all eigen values + for(int i = k; i < m1; i++) {//loop over previously computed eigenvalues + if(xu < wu[i]) {//check if previous eigenvalue had a better lower bound + xu = wu[i];//start from the better lower bound instead + break;//reference pseudocode: goto contin + }//reference pseudocode: end + }//reference pseudocode: end i + //reference pseudocode: contin + if(x0 > x[k]) x0 = x[k];//initial upper bound on eigen value k + while( x0 - xu > relfeh * (std::fabs(xu) + std::fabs(x0)) * 2 + eps1) {//keep iterating until we reach our precision goal + //update estimate and check for failure to converge + Real x1 = (xu + x0) / 2;//current estimate of eigen value is average of upper and lower bounds + if(z++ > limit) throw std::runtime_error("too many iterations computing legendre roots");//count iterations required + + //reference pseudocode: Sturms sequence + size_t a = n; + Real q = 1; + for(size_t i = 0; i < n; i++) { + q = - ( x1 + (q != 0 ? beta[i] / q : std::fabs(b[i]) / relfeh) ); + if(std::signbit(q)) --a; + }//reference pseudocode: end i + + //now update appropriate bound based on a + if(a > k) {//update the lower bound + if(a >= m1) { + xu = wu[n-1-m1 ] = x1;//update lower bound of smallest eigen value to calculate + } else { + xu = wu[a-1] = x1;//update lower bound of eigenvalue a+1 + if(x[a] > x1) x[a] = x1;//also update corresponding upper bound if possible (all eigenvalues are distinct) + } + } else {//update the upper bound + x0 = x1; + } + x[k] = (x0 + xu) / 2; + }//reference pseudocode: end x1 + }//reference pseudocode: end k + } + + //@brief : compute the real space coordinates of unprojected points in a square legendre grid (north hemisphere only) + //@param dim: side length of square legendre grid (must be odd) + //@param xyz: location to write real space coordiantes (dim * dim * 3) + template void normals(const size_t dim, Real * const xyz) { + static const Real kPi_4 = Real(0.7853981633974483096156608458199);//pi/4 + if(0 == dim % 2) throw std::runtime_error("only odd side lengths are supported"); + + //compute ring latitudes once + const int half = (int)(dim / 2); + std::vector cosLats(half); + roots(dim - 2, cosLats.data()); + + //loop over northern hemisphere compute + for(size_t j = 0; j < dim; j++) { + const int rj = int(j) - half;//how many rings away from the pole are we in the x direction + const size_t aj = (size_t)std::abs(rj);//|rj| + const Real y = (Real(j) / (dim - 1)) * 2 - 1;//[-1,1] + for(size_t i = 0; i < dim; i++) { + const int ri = int(i) - half;//how many rings away from the pole are we in the x direction + const size_t ai = std::abs(ri);//|ri| + const Real x = (Real(j) / (dim - 1)) * 2 - 1;//[-1,1] + const size_t ar = std::max(ai, aj);//how many rings away from the pole are we + + Real n[3]; + if(ar == 0) {//handle pole specially + n[0] = n[1] = 0; + n[2] = 1; + } else {//not on pole + const Real sX = Real(ri) / ar; + const Real sY = Real(rj) / ar; + Real x, y; + if(ai <= aj) { + const Real qq = kPi_4 * sX * sY; + x = sY * std::sin(qq); + y = sY * std::cos(qq); + } else { + const Real qq = kPi_4 * sY * sX; + x = sX * std::cos(qq); + y = sX * std::sin(qq); + } + const Real h = std::hypot(x, y); + n[2] = cosLats[ar-1];//get z from lookup table + n[0] = n[1] = std::sqrt(Real(1) - n[2] * n[2]);//sin(acos(z)) + n[0] *= x / h;//cos(atan2(y, x)) + n[1] *= y / h;//sin(atan2(y, x)) + } + std::copy(n, n + 3, xyz + 3 * (dim * j + i)); + } + } + } + + //@brief : compute the 4 bounding indices of for a given unit direction in a the north hemisphere of square legendre grid + //@param dim : side length of square legendre grid (must be odd) + //@param zLat: z coordinates of each ring in the projection (e.g. from cosLats) + //@param n : direction to get bounding indices for + //@param inds: location to write bounding indices + template void boundingInds(const size_t dim, Real const * const zLat, Real const * const n, size_t * const inds) { + if(0 == dim % 2) throw std::runtime_error("only odd side lengths are supported"); + + //get indices of bounding rings + const size_t ub = std::distance(zLat, std::lower_bound(zLat, zLat + dim, n[2], std::greater()) ); + const size_t rN = ub-1;//index of ring with more northern latitude than n + const size_t rS = ub == dim ? rN : ub;//index of ring with more southern latitude than n + + //get the index of the north pole + const size_t idxPole = (dim * dim) / 2;//index of north pole + const bool pole = rN == 0;//are we next to the north pole? + + //compute fractional progress around ring + const Real ax = std::fabs(n[0]); + const Real ay = std::fabs(n[1]); + Real theta = ( std::atan(std::min(ax,ay) / std::max(ax,ay)) * 4 ) / emsphinx::Constants::pi;//[-0.5 -> 0.5] + if(theta != theta) theta = 0;//ax == ay == 0; + + //initialize shift left/right or up/down in pixels + size_t delta[4] = {1,1,1,1}; + + //now handle octants of square grid separately + const bool nx = std::signbit(n[0]); + const bool ny = std::signbit(n[1]); + bool sub; + if(ay >= ax) {//[45,135] or [225,315] + //get indices of (0, +/-y) + if(ny) {//[225,315] + inds[0] = inds[1] = idxPole - rN * dim;//index of 270 degree point in ring rN + inds[2] = inds[3] = idxPole - rS * dim;//index of 270 degree point in ring rS + } else {//[45,135] + inds[0] = inds[1] = idxPole + rN * dim;//index of 90 degree point in ring rN + inds[2] = inds[3] = idxPole + rS * dim;//index of 90 degree point in ring rS + } + sub = nx; + } else {//[-45,45] or [135,225] + //get indices of (+/-x, 0) + if(nx) {//[135,225] + inds[0] = inds[1] = idxPole - rN ;//index of 180 degree point in ring rN + inds[2] = inds[3] = idxPole - rS ;//index of 180 degree point in ring rS + } else {//[-45,45] + inds[0] = inds[1] = idxPole + rN ;//index of 0 degree point in ring rN + inds[2] = inds[3] = idxPole + rS ;//index of 0 degree point in ring rS + } + sub = ny; + std::for_each(delta, delta+4, [dim](size_t& d){d *= dim;});//we need to do row shifts instead of column shifts + } + + delta[0] *= (size_t)std::ceil (theta * rN); + delta[1] *= (size_t)std::floor(theta * rN); + delta[2] *= (size_t)std::ceil (theta * rS); + delta[3] *= (size_t)std::floor(theta * rS); + + if(sub) + for(size_t i = 0; i < 4; i++) inds[i] -= delta[i]; + else + for(size_t i = 0; i < 4; i++) inds[i] += delta[i]; + } + } + + //@brief : extract a single ring from a row major pattern + //@param dim : side length of square in pixels + //@param ring: ring number to copy (0 for north pole, (dim-1)/2 for equator) + //@param ptr : pointer to start of hemisphere to copy from (dim * dim array in row major order) + //@param buff: location to write extracted ring + //@return : number of values copied + template + size_t readRing(const size_t dim, const size_t ring, T const * const ptr, T * const buff) { + //compute indicies of corners + const size_t even = 1 - (dim % 2);//1 if even 0 if odd + const size_t side = 2 * ring + 1 + even;//side length of ring to copy + const size_t pole = (dim * (dim + even) ) / 2;//index of north pole (or closest point for even sizes) + const size_t start = pole + ring ;//first point to copy + const size_t quad1 = start + dim * ring ;//corner in quadrant 1 + const size_t quad2 = quad1 - (side - 1);//corner in quadrant 2 + const size_t quad3 = quad2 - dim * (side - 1);//corner in quadrant 3 + const size_t quad4 = quad3 + (side - 1);//corner in quadrant 4 + + //compute offsets to quad2 and quad3 in the ring + const size_t b1 = ring; + const size_t b2 = b1 + side - 1; + const size_t b3 = b2 + side - 1; + const size_t b4 = b3 + side - 1; + + //copy data using access pattern that is sequential in the row major order + //this should be the best option for caching behavior since the ring major buffer is always relatively small + std:: copy(ptr + quad3, ptr + quad4 + 1, buff + b3);//copy from quadrant 3 corner to quadrant 4 corner + for(size_t i = 1-even; i < ring; i++) {//copy from quadrant 3/4 corner to y == 0 + buff[b3-i-even] = ptr[quad3 + dim * ( i + even)];//-x (quadrant 3 -> y == 0) + buff[b4+i+even] = ptr[quad4 + dim * ( i + even)];//+x (quadrant 4 -> y == 0) + } + for(size_t i = 0; i < ring; i++) {//copy from y == 0 to quadrant 2/1 corner + buff[b2+ring-i] = ptr[quad3 + dim * (ring + i + even)];//-x (y == 0 -> quadrant 2) + buff[ i] = ptr[quad4 + dim * (ring + i + even)];//+x (y == 0 -> quadrant 1) + } + std::reverse_copy(ptr + quad2, ptr + quad1 + 1, buff + b1);//copy from quadrant 1 corner to quadrant 2 corner + if(0 == ring && 0 == even) return 1; + return 4 * (side - 1); + } + + //@brief : write a single ring into a row major pattern + //@param dim : side length of square in pixels + //@param ring: ring number to write (0 for north pole, (dim-1)/2 for equator) + //@param ptr : pointer to start of hemisphere to write to (dim * dim array in row major order) + //@param buff: location to read ring from + //@return : number of values copied + template + size_t writeRing(const size_t dim, const size_t ring, T * const ptr, T const * const buff) { + //compute indicies of corners + const size_t even = 1 - (dim % 2);//1 if even 0 if odd + const size_t side = 2 * ring + 1 + even;//side length of ring to copy + const size_t pole = (dim * (dim + even) ) / 2;//index of north pole (or closest point for even sizes) + const size_t start = pole + ring ;//first point to copy + const size_t quad1 = start + dim * ring ;//corner in quadrant 1 + const size_t quad2 = quad1 - (side - 1);//corner in quadrant 2 + const size_t quad3 = quad2 - dim * (side - 1);//corner in quadrant 3 + const size_t quad4 = quad3 + (side - 1);//corner in quadrant 4 + + //compute offsets to quad2 and quad3 in the ring + const size_t b1 = ring; + const size_t b2 = b1 + side - 1; + const size_t b3 = b2 + side - 1; + const size_t b4 = b3 + side - 1; + + //copy data using access pattern that is sequential in the row major order + //this should be the best option for caching behavior since the ring major buffer is always relatively small + std:: copy(buff + b3, buff + b4 + 1, ptr + quad3);//copy from quadrant 3 corner to quadrant 4 corner + for(size_t i = 1-even; i < ring; i++) {//copy from quadrant 3/4 corner to y == 0 + ptr[quad3 + dim * ( i + even)] = buff[b3-i-even];//-x (quadrant 3 -> y == 0) + ptr[quad4 + dim * ( i + even)] = buff[b4+i+even];//+x (quadrant 4 -> y == 0) + } + for(size_t i = 0; i < ring; i++) {//copy from y == 0 to quadrant 2/1 corner + ptr[quad3 + dim * (ring + i + even)] = buff[b2+ring-i];//-x (y == 0 -> quadrant 2) + ptr[quad4 + dim * (ring + i + even)] = buff[ i];//+x (y == 0 -> quadrant 1) + } + std::reverse_copy(buff + b1, buff + b2 + 1, ptr + quad2);//copy from quadrant 1 corner to quadrant 2 corner + if(0 == ring && 0 == even) return 1; + return 4 * (side - 1); + } + + //@brief : compute quadrature weights for rings (w_y in equation 10 of Reinecke) + //@param dim: side length of square lambert projection to compute weights for + //@param lat: cosines of ring latitudes (symmetric across equator) + //@param wgt: location to write weights for each row + //@param skp: ring to exclude from weights (e.g. skip = 0 will exclude the poles) + //@reference: https://doi.org/10.1111/j.1365-246X.1994.tb03995.x + template + void computeWeightsSkip(const size_t dim, Real const * const lat, Real * const wgt, const size_t skp) { + //compute the cosine of the latitude of each ring directly + const size_t num = dim;//compute the number of rings + const size_t nMat = (num + 1) / 2 - 1;//only need north hemisphere (south by symmetry) + + //build matrix of the form a_ij = cos(2*i*latitude[j]) for Sneeuw equation (21) + //I'll compute them using Chebyshev recursion: cos(ni) = T_n(cos(i)) with: T_0(x) = 1, T_1(x) = x, T_{n}(x) = 2x T_{n-1}(x) - T_{n-2}(x) + std::vector a(nMat * nMat);//allocate matrix + Real * const x = a.data() + nMat;//we'll need the second row x = cos(2*theta) for every subsequent row + std::fill(a.begin(), a.begin() + nMat, Real(1));//cos(0*theta) along first row + for(size_t i = 0 ; i < skp ; i++) x[i] = lat[i ] * lat[i ] * 2 - 1;//double angle formula for cos(x) -> cos(2x) + for(size_t i = skp; i < nMat; i++) x[i] = lat[i+1] * lat[i+1] * 2 - 1;//double angle formula for cos(x) -> cos(2x) + + //now fill in remaining rows of matrix a + for(size_t j = 2; j < nMat; j++) {//loop over remaining rows using Chebyshev recursion to fill in cos(2*n*i) + Real const * const t2 = a.data() + (j-2) * nMat;//row of T_{n-2} + Real const * const t1 = a.data() + (j-1) * nMat;//row of T_{n-1} + Real * const tn = a.data() + j * nMat;//row of T_{n } + for(size_t i = 0; i < nMat; i++) tn[i] = x[i] * t1[i] * 2 - t2[i];//T_{n}(x) = 2x T_{n-1}(x) - T_{n-2}(x) + } + //build column vector b and solve for weights (A * wgt = b) + std::vector b(nMat); + for(int i = 0; i < nMat; i++) b[i] = Real(-1) / (4 * i * i - 1); + solve::lu(a.data(), wgt, b.data(), nMat);//solve A * wgt = b + + //compute the solid angle of a grid point + const size_t gridPoints = dim * dim * 2 - (dim - 1) * 4;//determine total number of points on sphere (equator has double cover) + const Real wn = emsphinx::Constants::pi2 * 4 / gridPoints;//solid angle of single point (solid angle of sphere / # grid points) + const Real w0 = wn * Real( ( dim - 2 ) * dim + 2 );//initial scaling factor (doesn't account for different # pts in each ring) + + //rescale weights by solid angle and ratio of points to equatorial points and use symmetry to fill southern hemisphere weights + const bool even = (0 == dim % 2);//even and odd are slightly different + const size_t offset = even ? 4 : 0; + const Real delta = std::accumulate(wgt, wgt + nMat, Real(0)) - 1;//should be 0 (weights should sum to 2, 1 for only northern hemisphere) + if(delta > std::cbrt(std::numeric_limits::epsilon()) / 64 ) throw std::runtime_error("insufficient precision to accurately compute ring wieghts"); + std::copy(wgt + skp, wgt + nMat, wgt + skp + 1);//correct alignment for missing ring + wgt[skp] = 0;//skipped ring has no weight + wgt[0] *= w0 / (even ? 4 : 1);//handle first ring / pole point specially (4 points for even, 1 point for odd sizes) + for(size_t i = 1; i < nMat+1; i++) wgt[i] *= w0 / (8 * i + offset);//scale to correct for different ring sizes to w0 / # points in this ring + } + + //@brief : compute the cosines of ring latitudes for a given square grid + //@param dim : side length of square projection to compute ring latitudes for + //@param type: type of square projection to compute latitudes for + //@return : cosine(latitudes) from north pole -> equator + template typename std::vector cosLats(const size_t dim, const Layout type) { + std::vector cLats((dim+1) / 2);//cosines of ring latitudes + switch(type) { + case Layout::Lambert: + lambert::cosLats(dim, cLats.data()); + break; + + case Layout::Legendre: + cLats[0] = 1;//manually specify pole + legendre::roots(dim-2, cLats.data()+1);//fill in north ring latitude cosines + } + return cLats; + } + + //@brief : compute the normals of the northern hemisphere for a given square grid + //@param dim : side length of square projection to compute ring latitudes for + //@param type: type of square projection to compute latitudes for + //@return : {x,y,z} normals for north pole + template typename std::vector normals(const size_t dim, const Layout type) { + std::vector normals(dim * dim * 3);//cosines of ring latitudes + switch(type) { + case Layout ::Lambert: + lambert ::normals(dim, normals.data()); + break; + + case Layout::Legendre: + legendre::normals(dim, normals.data()); + break; + } + return normals; + } + + //@brief : compute the solid angle correction of each ring for a given square grid + //@param dim : side length of square lambert projection to compute solid angles for + //@param type: type of square projection to compute latitudes for + //@return : solide angle correction from north pole -> equator (actual pixel size / average pixel size) + template typename std::vector solidAngles(const size_t dim, const Layout type) { + //compute cosines of ring lattidues and average pixel size + std::vector cLats = cosLats(dim, type); + const size_t numPix = dim * dim * 2 - (dim - 1) * 4; + // const Real avgPix = emsphinx::Constants::pi2 * Real(2) / numPix;//4 pi evenly split over pixels + const Real avgPix = Real(2) / numPix;//2 evenly split over pixels (2 pi will cancel out) + + //convert form cosine lattidues of center to cosine latitudes of mid points + std::vector cSplits(cLats.size() - 1); + std::transform(cLats.cbegin(), cLats.cend() - 1, cLats.cbegin() + 1, cSplits.begin(), [](const Real& cA, const Real& cB) { + return ( std::sqrt( (Real(1) + cA) * (Real(1) + cB) ) - std::sqrt( (Real(1) - cA) * (Real(1) - cB) ) ) / Real(2);//cos((a+b)/2) where a = acos(cA), b = acos(cB) + }); + cSplits.push_back(-cSplits.back());//symmetric about equator + + //convert from cosine latitudes of midpoints to spherical cap areas + for(Real& c : cSplits) c = (Real(1) - c);// * emsphinx::Constants::pi2; (we're just going to divide by 2 pi later) + + //convert from spherical cap areas to area of ring + std::adjacent_difference(cSplits.cbegin(), cSplits.cend(), cSplits.begin()); + + //convert from ring area to pixel area and normalize by average pixel size + size_t num = 8; + if(0 == dim % 2) {//even [4,12,20] + cSplits[0] /= avgPix * Real(4); + num = 12; + } else {//odd [1,8,16,24,...] + cSplits[0] /= avgPix * Real(1); + } + for(size_t i = 1; i < cSplits.size(); i++) { + cSplits[i] /= avgPix * Real(num); + num += 8; + } + return cSplits; + } + + //@brief : convert from a vecotrized index to a ring number + //@param dim: side length of square lambert projection to compute index for + //@param idx: vectorized index (y * dim + x) to compute ring number for [must be in north hemisphere i.e. < dim * dim] + //@return : ring number (e.g. for indexing into cosLats) + size_t ringNum(const size_t dim, const size_t idx) { + //convert from vectorized to x/y indices + const int y = int(idx / dim); + const int x = int(idx - y * dim); + + //compute distance to center pixel + int d2 = int(dim / 2); + size_t dx = (size_t)std::abs(x - d2);//x distance from center + size_t dy = (size_t)std::abs(y - d2);//y distance from center + if(0 == dim % 2) { + //even case is a little more complicated since the center isn't on a pixel + ++d2;//move to other corner of northern most ring + dx = std::min(dx, (size_t)std::abs(x - d2));//choose whichever x distance is smallest + dy = std::min(dy, (size_t)std::abs(y - d2));//choose whichever x distance is smallest + } + return std::max(dx, dy);//ring is maximum distance + } + } + +} + +#endif//_SQUARE_SHT_H_ diff --git a/include/sht/wigner.hpp b/include/sht/wigner.hpp new file mode 100644 index 0000000..065e838 --- /dev/null +++ b/include/sht/wigner.hpp @@ -0,0 +1,857 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _WIGNER_H_ +#define _WIGNER_H_ + +#include + +namespace emsphinx { + + namespace wigner { + //@remarks + //everyone seems to use different variables for degree/order of wigner functions + //wigner (lowercase) d functions use the notation of: Fukushima, Toshio. (2016). Numerical computation of Wigner's d-function of arbitrary high degree and orders by extending exponent of floating point numbers. + //https://doi.org/10.13140/RG.2.2.31922.20160 + //that means I use j, k, and m for degree and order such that wigner (lowercase) d functions are: + // d^j_{k, m}(beta) = sqrt( ( (j+k)! * (j-k)! ) / ( (j+m)! * (j-m)! ) ) * pow(cos(beta / 2), k+m) * pow(sin(beta / 2), k-m) * P^{k-m, k+m}_{j-k}(cos(beta)) + //which is the the equation 1 in the Fukushima reference + //P^{a, b}_{n}(x) is the Jacobi polynomial of degree n and orders a/b evaluated at x e.g. the mathematica function JacobiP[n, a, b, x] + //there additionally several conventions for the wigner (uppercase) D function + //I'll use the convention such that for passive ZYZ euler angles {alpha, beta, gamma) the SHT of a function can be rotated by aRot^l_k = \sum_{m=-1}^l a^l_m D^l_{k,m}(ZYZ) + // + //the Wigner (lowercase) d function is computed recursively so it is expensive to calculate a single value + //table functions are provided to generate d^j_{k,m} efficiently for all values of j/k/m (to a desired max j) + //the Wigner (uppercase) D function required complex exponentials and a (lowercase) d function so it is even more expensive + //functions to evaluate a single upper/lowercase wigner value are provided primarily for sanity checks and comparision against other conventions + // + //if D^j_{k,m}(\alpha, \beta, \gamma) is needed for all j, k, and m it is best to + // -compute d^j_{k,m}(\beta) for all values (with a table function) + // -loop over m computing exp(I*m*alpha) + // -loop over k computing exp(I*k*gamma) + // -compute D^j_{k,m}(\alpha, \beta, \gamma) == d^j_{k,m}(\beta) * exp(I*m*alpha) * exp(I*k*gamma) + + //@brief : compute Wigner (lowercase) d function (also called reduced wigner d) + //@param j : degree in d^j_{k,m}(beta) + //@param k : first order in d^j_{k,m}(beta) + //@param m : second order in d^j_{k,m}(beta) + //@param t : cos(beta) + //@param nB: true/false for negative/positive beta + //@return : d^j_{k,m}(beta) + //@note : NAN when j < max(|k|, |m|) + //@note : equivalent to D^j_{k,m}({0, beta, 0}) + //@note : equivalent to the mathematica function WignerD[{j, k, m}, beta] + template Real d(const int64_t j, const int64_t k, const int64_t m, const Real t, const bool nB); + + //@brief : compute Wigner (lowercase) d function (also called reduced wigner d) at pi/2 + //@param j: degree + //@param k: first order + //@param m: second order + //@return : d^j_{k,m}(\frac{\pi}{2}) + //@note : NAN when j < max(|k|, |m|) + //@note : equivalent to the mathematica function WignerD[{j, k, m}, Pi/2] + template Real d(const int64_t j, const int64_t k, const int64_t m); + + //@brief : compute symmetry of wigner (lowercase) d function @ pi/2 + //@param j: degree + //@param k: first order + //@param m: second order + //@return : +/-1 such that dSign(j, k, m) * d(j, |k|, |m|) == d(j, k, m) + int dSign(const int64_t j, const int64_t k, const int64_t m); + + //@brief : compute Wigner (uppercase) D function + //@param j : degree in d^j_{k,m}(beta) + //@param k : first order in d^j_{k,m}(beta) + //@param m : second order in d^j_{k,m}(beta) + //@param eu: ZYZ euler angles + //@return : D^j_{k,m}(eu) + //@note : NAN when j < max(|k|, |m|) + //@note : equivalent to the mathematica function WignerD[{j, k, m}, eu[2], eu[1], eu[0]] + //@note : for ZYZ euler angles this d^j_{k,m}(eu[1]) * exp(I*m*eu[0] + I*k*eu[2]) + template std::complex D(const int64_t j, const int64_t k, const int64_t m, Real const * const eu); + + //@brief : compute a table of Wigner (lowercase) d functions at 0 <= beta <= pi + //@param jMax : maximum order to compute table for + //@param t : cos(beta) + //@param table: location to write d^j_{k,m}(beta) for all non negative j, k, m (must have space for jMax * jMax * jMax * 2 Reals) + //@note : the table has unused (an uninitialized space) for j < max(|k|, |m|) + //@note : d^j_{k,m}( beta) at table[(k * jMax * jMax + m * jMax + j)*2 + 0] + //@note : d^j_{k,m}(pi-beta) at table[(k * jMax * jMax + m * jMax + j)*2 + 1] + //@note : negative k/m values can be found with the following symmetry relationships + // d^j_{-k,-m}( beta) = (-1)^(k-m) d^j_{k,m}( beta) + // d^j_{ k,-m}( beta) = (-1)^(j+k) d^j_{k,m}(pi - beta) + // d^j_{-k, m}( beta) = (-1)^(j+m) d^j_{k,m}(pi - beta) + template void dTable(const size_t jMax, const Real t, const bool nB, Real * const table); + + //@brief : compute a table of Wigner (lowercase) d functions at 0 <= beta <= pi [with precomputed coefficient tables] + //@param jMax : maximum order to compute table for + //@param t : cos(beta) + //@param table: location to write d^j_{k,m}(beta) for all non negative j, k, m (must have space for jMax * jMax * jMax * 2 Reals) + //@param pE : precomputed table of e_km + //@param pW : precomputed table of w_jkm + //@param pB : precomputed table of b_jkm + //@note : the table has unused (an uninitialized space) for j < max(|k|, |m|) + //@note : d^j_{k,m}( beta) at table[(k * jMax * jMax + m * jMax + j)*2 + 0] + //@note : d^j_{k,m}(pi-beta) at table[(k * jMax * jMax + m * jMax + j)*2 + 1] + //@note : negative k/m values can be found with the following symmetry relationships + // d^j_{-k,-m}( beta) = (-1)^(k-m) d^j_{k,m}( beta) + // d^j_{ k,-m}( beta) = (-1)^(j+k) d^j_{k,m}(pi - beta) + // d^j_{-k, m}( beta) = (-1)^(j+m) d^j_{k,m}(pi - beta) + template void dTablePre(const size_t jMax, const Real t, const bool nB, Real * const table, Real const * const pE, Real const * const pW, Real const * const pB); + + //@brief : fill precomputed factor tables for dTablePre + //@param jMax : maximum order to compute table for + //@param pE : location to write precomputed table of e_km + //@param pW : location to write precomputed table of w_jkm + //@param pB : location to write precomputed table of b_jkm + template void dTablePreBuild(const size_t jMax, Real * const pE, Real * const pW, Real * const pB); + + //@brief : compute a table of Wigner (lowercase) d functions at pi/2 + //@param jMax : max j + //@param table: location to write d^j_{k,m}(\frac{\pi}{2}) for j = [0,jMax), k = [0,jMax), m = [0, jMax) (jMax^3 values) + //@param trans: true/false to transpose k/m indices + //@note : d^j_{k,m} located at k * jMax * jMax + m * jMax + j (trans = false) + //@note : d^j_{k,m} located at m * jMax * jMax + k * jMax + j (trans = true ) + template void dTable(const size_t jMax, Real * const table, const bool trans = false); + + //@brief : rotate the spherical harmonic transformation of a real function + //@param bw : bandwidth of harmonic coefficients (max l exclusive) + //@param alm: spherical harmonic coefficients to rotate with \hat{a}^l_{m} at alm[m * bw + j] + //@param blm: location to write rotated spherical harmonic coefficients with \hat{b}^l_{m} at blm[m * bw + j] + //@param zyz: rotation to apply as zyz euler angles + //@note : b^l_m = \sum_{n=-1}^l a^l_n D^l_{m,n}(qu) + template + void rotateHarmonics(const size_t bw, std::complex const * const alm, std::complex * const blm, Real const * const zyz); + + //@brief : compute first derivative of Wigner (lowercase) d function (also called reduced wigner d) with respect to beta + //@param j : degree in (d/dBeta) d^j_{k,m}(beta) + //@param k : first order in (d/dBeta) d^j_{k,m}(beta) + //@param m : second order in (d/dBeta) d^j_{k,m}(beta) + //@param t : cos(beta) + //@param nB: true/false for negative/positive beta + //@return : (d/dBeta) d^j_{k,m}(beta) + //@note : NAN when j < max(|k|, |m|) + //@note : equivalent to the mathematica function D[WignerD[{j, k, m}, beta], beta] + //@note : negative k/m values have the following symmetry relationships + // dPrime^j_{-k,-m}( beta) = (-1)^(k+m ) dPrime^j_{k,m}( beta) + // dPrime^j_{ k,-m}( beta) = (-1)^(j+k+1) dPrime^j_{k,m}(pi - beta) + // dPrime^j_{-k, m}( beta) = (-1)^(j+m+1) dPrime^j_{k,m}(pi - beta) + template Real dPrime(const int64_t j, const int64_t k, const int64_t m, const Real t, const bool nB); + + //@brief : compute second derivative of Wigner (lowercase) d function (also called reduced wigner d) with respect to beta + //@param j : degree in (d/dBeta)^2 d^j_{k,m}(beta) + //@param k : first order in (d/dBeta)^2 d^j_{k,m}(beta) + //@param m : second order in (d/dBeta)^2 d^j_{k,m}(beta) + //@param t : cos(beta) + //@param nB: true/false for negative/positive beta + //@return : (d/dBeta)^2 d^j_{k,m}(beta) + //@note : NAN when j < max(|k|, |m|) + //@note : equivalent to the mathematica function D[WignerD[{j, k, m}, beta], {beta, 2}] + //@note : negative k/m values have the following symmetry relationships + // dPrime2^j_{-k,-m}( beta) = (-1)^(k+m) dPrime2^j_{k,m}( beta) + // dPrime2^j_{ k,-m}( beta) = (-1)^(j+k) dPrime2^j_{k,m}(pi - beta) + // dPrime2^j_{-k, m}( beta) = (-1)^(j+m) dPrime2^j_{k,m}(pi - beta) + template Real dPrime2(const int64_t j, const int64_t k, const int64_t m, const Real t, const bool nB); + } +} + +//////////////////////////////////////////////////////////////////////// +// Implementations // +//////////////////////////////////////////////////////////////////////// + +#include +#include + +namespace emsphinx { + + namespace wigner { + //simplified helper functions to compute Wigner (lowercase) d functions d^j_{k,m}(beta) for 0 <= beta <= pi and integer j,k,m + //all functions use the notation of: Fukushima, Toshio. (2016). Numerical computation of Wigner's d-function of arbitrary high degree and orders by extending exponent of floating point numbers. + //https://doi.org/10.13140/RG.2.2.31922.20160 + //blocks functions share the same basic structure but the factor of 2 has been removed since only whole (not half) integers are needed + + //@brief : compute intermediate recursion coefficient u_{j,k,m} (equation 13) + //@param j : degree in d^j_{k,m} + //@param k : first order in d^j_{k,m} + //@param m : second order in d^j_{k,m} + //@param t/tc: cos(beta) / 1 - cos(beta) + //@return : recursion coefficient + //@note : _0, _1, _2 for 0 <= beta < pi / 2, beta == pi / 2, and pi / 2 < beta <= pi / 2 respectively + template inline Real u_jkm_0(const int64_t j, const int64_t k, const int64_t m, const Real tc) {return -tc * ((j - 1) * j) - (k * m - (j - 1) * j);} + inline int64_t u_jkm_1(const int64_t j, const int64_t k, const int64_t m ) {return - k * m ;} + template inline Real u_jkm_2(const int64_t j, const int64_t k, const int64_t m, const Real t ) {return t * ((j - 1) * j) - k * m ;} + + //@brief : compute intermediate recursion coefficient v_{j,k,m} (equation 14) + //@param j : degree in d^j_{k,m} + //@param k : first order in d^j_{k,m} + //@param m : second order in d^j_{k,m} + //@return : recursion coefficient + template inline Real v_jkm (const int64_t j, const int64_t k, const int64_t m ) {return std::sqrt( Real( (j+k-1) * (j-k-1) * (j+m-1) * (j-m-1) ) ) * (j ) ;} + + //@brief : compute intermediate recursion coefficient w_{j,k,m} (equation 15) + //@param j : degree in d^j_{k,m} + //@param k : first order in d^j_{k,m} + //@param m : second order in d^j_{k,m} + //@return : recursion coefficient + //@note : this function is most susceptible to integer overflow (particularly at k = m = 0) with a max k of only 215 for 32 bit ints (2^31-1)^(1/4), 64 bit integers buys up to ~55k which should be plenty + template inline Real w_jkm (const int64_t j, const int64_t k, const int64_t m ) {return Real(1) / ( std::sqrt( Real( (j+k ) * (j-k ) * (j+m ) * (j-m ) ) ) * (j-1) );} + + //@brief : compute intermediate recursion coefficient a_{j,k,m} (equation 11) + //@param j : degree in d^j_{k,m} + //@param k : first order in d^j_{k,m} + //@param m : second order in d^j_{k,m} + //@param t/tc: cos(beta) / 1 - cos(beta) + //@return : recursion coefficient + //@note : _0, _1, _2 for 0 <= beta < pi / 2, beta == pi / 2, and pi / 2 < beta <= pi / 2 respectively + template inline Real a_jkm_0(const int64_t j, const int64_t k, const int64_t m, const Real tc) {return w_jkm(j, k, m) * ( u_jkm_0(j, k, m, tc) * (2*j-1) );} + template inline Real a_jkm_1(const int64_t j, const int64_t k, const int64_t m ) {return w_jkm(j, k, m) * ( u_jkm_1 (j, k, m ) * (2*j-1) );} + template inline Real a_jkm_2(const int64_t j, const int64_t k, const int64_t m, const Real t ) {return w_jkm(j, k, m) * ( u_jkm_2(j, k, m, t ) * (2*j-1) );} + + //@brief : compute intermediate recursion coefficient a_{j,k,m} (equation 11) [with precomputed w_jkm] + //@param w : w(j,k,m) + //@param j : degree in d^j_{k,m} + //@param k : first order in d^j_{k,m} + //@param m : second order in d^j_{k,m} + //@param t/tc: cos(beta) / 1 - cos(beta) + //@return : recursion coefficient + //@note : _0, _1, _2 for 0 <= beta < pi / 2, beta == pi / 2, and pi / 2 < beta <= pi / 2 respectively + template inline Real a_jkm_0_pre(const Real w, const int64_t j, const int64_t k, const int64_t m, const Real tc) {return w * ( u_jkm_0(j, k, m, tc) * (2*j-1) );} + template inline Real a_jkm_1_pre(const Real w, const int64_t j, const int64_t k, const int64_t m ) {return w * ( u_jkm_1 (j, k, m ) * (2*j-1) );} + template inline Real a_jkm_2_pre(const Real w, const int64_t j, const int64_t k, const int64_t m, const Real t ) {return w * ( u_jkm_2(j, k, m, t ) * (2*j-1) );} + + //@brief : compute intermediate recursion coefficient a_{j,k,m} (equation 12) + //@param j : degree in d^j_{k,m} + //@param k : first order in d^j_{k,m} + //@param m : second order in d^j_{k,m} + //@return : recursion coefficient + template inline Real b_jkm (const int64_t j, const int64_t k, const int64_t m ) {return w_jkm(j, k, m) * v_jkm(j, k, m);} + + //@brief : compute recursion seed coefficient u_{k,m} (equation 23) + //@param k : first order in d^j_{k,m} + //@param m : second order in d^j_{k,m} + //@return : recursion seed + template inline Real u_km_0 ( const int64_t k, const int64_t m, const Real tc) {return (-tc * (k + 1) - (m - 1 - k));} + template inline Real u_km_1 ( const int64_t k, const int64_t m ) {return Real( - m );} + template inline Real u_km_2 ( const int64_t k, const int64_t m, const Real t ) {return ( t * (k + 1) - m );} + + //@brief : compute recursion seed coefficient a_{k,m} (equation 22) + //@param k : first order in d^j_{k,m} + //@param m : second order in d^j_{k,m} + //@param t/tc: cos(beta) / 1 - cos(beta) + //@return : recursion seed + //@note : _0, _1, _2 for 0 <= beta < pi / 2, beta == pi / 2, and pi / 2 < beta <= pi / 2 respectively + template inline Real a_km_0 ( const int64_t k, const int64_t m, const Real tc) {return std::sqrt( Real( 2*k+1 ) / ( (k+m+1) * (k-m+1) ) ) * u_km_0(k, m, tc);} + template inline Real a_km_1 ( const int64_t k, const int64_t m ) {return std::sqrt( Real( 2*k+1 ) / ( (k+m+1) * (k-m+1) ) ) * u_km_1(k, m );} + template inline Real a_km_2 ( const int64_t k, const int64_t m, const Real t ) {return std::sqrt( Real( 2*k+1 ) / ( (k+m+1) * (k-m+1) ) ) * u_km_2(k, m, t );} + + //@brief: compute recursion seed coefficient e_{k,m} = \sqrt{\frac{(2k)!}{(k+m)!(k-m)!}} recursively (m <= k) (equation 21) + //@param k: k in d^k_{k,m} + //@param m: m in d^k_{k,m} + //@return: e_km where d^k_{k,m} = 2^-k * e_km + template inline Real e_km(const int64_t k, const int64_t m) { + Real e_lm = 1;//e_mm; + for(int64_t l = m+1; l <= k; l++) e_lm *= std::sqrt( Real( l*(2*l-1) ) / ( 2 * (l+m) * (l-m) ) ) * 2; + return e_lm;//e_km + } + + //@brief : compute Wigner (lowercase) d function (also called reduced wigner d) + //@param j : degree in d^j_{k,m}(beta) + //@param k : first order in d^j_{k,m}(beta) + //@param m : second order in d^j_{k,m}(beta) + //@param t : cos(beta) + //@param nB: true/false for negative/positive beta + //@return : d^j_{k,m}(beta) + //@note : NAN when j < max(|k|, |m|) + //@note : equivalent to D^j_{k,m}({0, beta, 0}) + //@note : equivalent to the mathematica function WignerD[{j, k, m}, beta] + template + Real d(const int64_t j, const int64_t k, const int64_t m, const Real t, const bool nB) { + //require 0 <= m <= k <= j and beta >= 0 (handle other cases with symmetry) + if(nB) { //d^j_{ k, m}(-beta) = d^j_{m,k}( beta) + return d(j, m, k, t, false); //equation 5 + } else if(k < 0 && m < 0) {//d^j_{-k,-m}( beta) = (-1)^( k- m) d^j_{k,m}( beta) + const int sign = (k-m) % 2 == 0 ? 1 : -1; + return d(j, -k, -m, t, false) * sign;//equation 6 + } else if( m < 0) {//d^j_{ k,-m}( beta) = (-1)^(j+ k+2m) d^j_{k,m}(pi - beta) + const int sign = (j+k) % 2 == 0 ? 1 : -1; + return d(j, k, -m, -t, false) * sign;//equation 7 + } else if(k < 0 ) {//d^j_{-k, m}( beta) = (-1)^(j+2k+3m) d^j_{k,m}(pi - beta) + const int sign = (j+m) % 2 == 0 ? 1 : -1; + return d(j, -k, m, -t, false) * sign;//equation 8 + } else if(k < m ) {//d^j_{ m, k}( beta) = (-1)^( k- m) d^j_{k,m}( beta) + const int sign = (k-m) % 2 == 0 ? 1 : -1; + return d(j, m, k, t, false) * sign;//equation 9 + } + + if(j < k) return NAN; + + //determine if beta is < (0), > (2), or = (1) to pi/2 + const size_t type = t > 0 ? 0 : (t < 0 ? 2 : 1); + const Real tc = Real(1) - t; + + //compute powers of cos/sin of beta / 2 + const Real c2 = std::sqrt( (Real(1) + t) / 2 );//cos(acos(t)) == cos(beta / 2), always positive since at this point 0 <= beta <= pi + const Real s2 = std::sqrt( (Real(1) - t) / 2 );//sin(acos(t)) == sin(beta / 2), always positive since at this point 0 <= beta <= pi + const Real cn = std::pow(c2, Real(k+m));//equation 20 for n = k+m + const Real sn = std::pow(s2, Real(k-m));//equation 20 for n = k-m + + //compute first term for three term recursion + const Real d_kkm = cn * sn * e_km(k, m);//equation 18, d^k_{k, m}(beta) + if(j == k ) return d_kkm;//if j == k we're done + + //compute second term for three term recursion + Real a_km; + switch(type) { + case 0: a_km = a_km_0(k, m, tc); break;//beta < pi/2 + case 1: a_km = a_km_1(k, m ); break;//beta == pi/2 + case 2: a_km = a_km_2(k, m, t ); break;//beta > pi/2 + } + const Real d_k1km = d_kkm * a_km;//equation 19, d^{k+1}_{k, m}(beta) + if(j == k+1) return d_k1km;//if j == k + 1 we're done + + //recursively compute by degree to j + Real d_ikm; + Real d_i2km = d_kkm ; + Real d_i1km = d_k1km; + switch(type) { + case 0://beta < pi/2 + for(int64_t i = k + 2; i <= j; i++) { + d_ikm = a_jkm_0(i, k, m, tc) * d_i1km - b_jkm(i, k, m) * d_i2km;//equation 10, d^i_{k, m}(beta) + d_i2km = d_i1km; + d_i1km = d_ikm ; + } + break; + case 1://beta == pi/2 + for(int64_t i = k + 2; i <= j; i++) { + d_ikm = a_jkm_1(i, k, m ) * d_i1km - b_jkm(i, k, m) * d_i2km;//equation 10, d^i_{k, m}(beta) + d_i2km = d_i1km; + d_i1km = d_ikm ; + } + break; + case 2://beta > pi/2 + for(int64_t i = k + 2; i <= j; i++) { + d_ikm = a_jkm_2(i, k, m, t ) * d_i1km - b_jkm(i, k, m) * d_i2km;//equation 10, d^i_{k, m}(beta) + d_i2km = d_i1km; + d_i1km = d_ikm ; + } + break; + } + return d_ikm; + } + + //@brief : compute Wigner (lowercase) d function (also called reduced wigner d) at pi/2 + //@param j: degree + //@param k: first order + //@param m: second order + //@return : d^j_{k,m}(\frac{\pi}{2}) + //@note : NAN when j < max(|k|, |m|) + //@note : equivalent to the mathematica function WignerD[{j, k, m}, Pi/2] + template Real d(const int64_t j, const int64_t k, const int64_t m) { + //require 0 <= m <= k <= j (handle with symmetry where possible) + if(k < 0 && m < 0) {//d^j_{-k,-m} = (-1)^( k- m) d^j_{k,m} + return ( k- m) % 2 == 0 ? d(j, -k, -m) : -d(j, -k, -m); + } else if(m < 0) {//d^j_{ k,-m} = (-1)^(j+ k+2m) d^j_{k,m} + return (j+ k+2*m) % 2 == 0 ? d(j, k, -m) : -d(j, k, -m); + } else if(k < 0) {//d^j_{-k, m} = (-1)^(j+2k+3m) d^j_{k,m} + return (j+2*k+3*m) % 2 == 0 ? d(j, -k, m) : -d(j, -k, m); + } else if(k < m) {//d^j_{ m, k} = (-1)^( k- m) d^j_{k,m} + return ( k- m) % 2 == 0 ? d(j, m, k) : -d(j, m, k); + } + if(j < k) return NAN; + + //compute first two terms for three term recursion + const Real d_kkm = std::pow(Real(2), Real(-k)) * e_km(k, m);//equation 18, d^k_{k, m}(pi/2) + if(j == k ) return d_kkm; + const Real d_k1km = d_kkm * a_km_1(k, m);//equation 19, d^{k+1}_{k, m}(pi/2) + if(j == k+1) return d_k1km; + + //recursively compute + Real d_ikm; + Real d_i2km = d_kkm ; + Real d_i1km = d_k1km; + for(int64_t i = k + 2; i <= j; i++) { + d_ikm = a_jkm_1(i, k, m) * d_i1km - b_jkm(i, k, m) * d_i2km;//equation 10, d^i_{k, m}(pi/2) + d_i2km = d_i1km; + d_i1km = d_ikm ; + } + return d_ikm; + } + + //@brief : compute symmetry of wigner (lowercase) d function @ pi/2 + //@param j: degree + //@param k: first order + //@param m: second order + //@return : +/-1 such that dSign(j, k, m) * d(j, |k|, |m|) == d(j, k, m) + int dSign(const int64_t j, const int64_t k, const int64_t m) { + if(k < 0 && m < 0) {//d^j_{-k,-m} = (-1)^( k- m) d^j_{k,m} + return (k-m) % 2 == 0 ? 1 : -1; + } else if(m < 0) {//d^j_{ k,-m} = (-1)^(j+ k+2m) d^j_{k,m} + return (j+k) % 2 == 0 ? 1 : -1; + } else if(k < 0) {//d^j_{-k, m} = (-1)^(j+2k+3m) d^j_{k,m} + return (j+m) % 2 == 0 ? 1 : -1; + } + return 1; + } + + //@brief : compute Wigner (uppercase) D function + //@param j : degree in d^j_{k,m}(beta) + //@param k : first order in d^j_{k,m}(beta) + //@param m : second order in d^j_{k,m}(beta) + //@param eu: ZYZ euler angles + //@return : D^j_{k,m}(eu) + //@note : NAN when j < max(|k|, |m|) + //@note : equivalent to the mathematica function WignerD[{j, k, m}, eu[2], eu[1], eu[0]] + //@note : for ZYZ euler angles this d^j_{k,m}(eu[1]) * exp(I*m*eu[0] + I*k*eu[2]) + template std::complex D(const int64_t j, const int64_t k, const int64_t m, Real const * const eu) { + const Real sum = eu[0] * m + eu[2] * k; + return std::complex(std::cos(sum), std::sin(sum)) * d(j, k, m, std::cos(eu[1]), std::signbit(eu[1])); + } + + //@brief : compute a table of Wigner (lowercase) d functions at 0 <= beta <= pi + //@param jMax : maximum order to compute table for + //@param t : cos(beta) + //@param table: location to write d^j_{k,m}(beta) for all non negative j, k, m (must have space for jMax * jMax * jMax * 2 Reals) + //@note : the table has unused (an uninitialized space) for j < max(|k|, |m|) + //@note : d^j_{k,m}( beta) at table[(k * jMax * jMax + m * jMax + j)*2 + 0] + //@note : d^j_{k,m}(pi-beta) at table[(k * jMax * jMax + m * jMax + j)*2 + 1] + //@note : negative k/m values can be found with the following symmetry relationships + // d^j_{-k,-m}( beta) = (-1)^(k-m) d^j_{k,m}( beta) + // d^j_{ k,-m}( beta) = (-1)^(j+k) d^j_{k,m}(pi - beta) + // d^j_{-k, m}( beta) = (-1)^(j+m) d^j_{k,m}(pi - beta) + template + void dTable(const size_t jMax, const Real t, const bool nB, Real * const table) { + /* + //naive implementation (has a bunch of redundant calculations) + for(int k = 0; k < jMax; k++) { + for(int m = 0; m < jMax; m++) { + for(int j = 0; j < jMax; j++) { + table[k * jMax * jMax * 2 + m * jMax * 2 + j * 2 + 0] = d(j, k, m, t, false);//d^j_{k, m}(beta) + table[k * jMax * jMax * 2 + m * jMax * 2 + j * 2 + 1] = d(j, k, m, -t, false);//d^j_{k, m}(pi - beta) + } + } + } + */ + + //determine which branch of function is needed and compute cos/sin of half angles + const bool isType0 = !std::signbit(t);//is this type 0 or type 2? (type 1 will be grouped with type 0) + const Real tc = Real(1) - t; + const Real tcN = Real(1) + t;//tc for -t + const Real c2 = std::sqrt(tcN / 2);//cos(acos(t)) == cos(beta / 2), always positive since at this point 0 <= beta <= pi + const Real s2 = std::sqrt(tc / 2);//sin(acos(t)) == sin(beta / 2), always positive since at this point 0 <= beta <= pi + + //get function pointers to avoid repeated branch in loop + Real (* const a_kmFunc )( const int64_t, const int64_t, const Real) = isType0 ? a_km_0 : a_km_2 ; + Real (* const a_kmFuncN )( const int64_t, const int64_t, const Real) = isType0 ? a_km_2 : a_km_0 ; + Real (* const a_jkmFunc )(const int64_t, const int64_t, const int64_t, const Real) = isType0 ? a_jkm_0 : a_jkm_2; + Real (* const a_jkmFuncN)(const int64_t, const int64_t, const int64_t, const Real) = isType0 ? a_jkm_2 : a_jkm_0; + const Real t0 = isType0 ? tc : t ; + const Real tN = isType0 ? -t : tcN; + + //precompute integer powers of c2 and s2 + std::vector work(jMax * 4); + Real* const pc2 = work.data(); + Real* const ps2 = work.data() + jMax * 2; + for(size_t i = 0; i < jMax * 2; i++) { + pc2[i] = std::pow(c2, Real(i)); + ps2[i] = std::pow(s2, Real(i)); + } + + //fill table + Real* pK = table;//table + k * jMax * jMAx * 2 + for(size_t k = 0; k < jMax; k++) { + Real* pKM = pK ;//table + k * jMax * jMax * 2 + m * jMax * 2 + Real* pMK = table + k * jMax * 2;//table + m * jMax * jMax * 2 + k * jMax * 2 + for(size_t m = 0; m <= k; m++) { + //determine sign change for swapping k/m + int sign = 1; + int signN = (k-m) % 2 == 0 ? 1 : -1;//symmetry from eq 9 + if(nB) std::swap(sign, signN); + + //compute powers of cos/sin of beta / 2 + const Real cn = pc2[k+m];//std::pow(c2, k+m);//equation 20 for n = k+m for t + const Real sn = ps2[k-m];//std::pow(s2, k-m);//equation 20 for n = k-m for t + const Real cnN = ps2[k+m];//std::pow(s2, k+m);//equation 20 for n = k+m for -t + const Real snN = pc2[k-m];//std::pow(c2, k-m);//equation 20 for n = k-m for -t + + //compute first term for three term recursion + const Real ekm = e_km(k, m); + const Real d_kkm = cn * sn * ekm;//equation 18, d^k_{k, m}(beta) for t + const Real d_kkmN = cnN * snN * ekm;//equation 18, d^k_{k, m}(beta) for -t + pKM[k * 2 + 0] = d_kkm * sign ; + pMK[k * 2 + 0] = d_kkm * signN;//symmetry from eq 9 + pKM[k * 2 + 1] = d_kkmN * sign ; + pMK[k * 2 + 1] = d_kkmN * signN;//symmetry from eq 9 + + if(k+1 < jMax) {//if j == k we're done + //compute second term for three term recursion + const Real a_km = a_kmFunc (k, m, t0); + const Real a_kmN = a_kmFuncN(k, m, tN); + const Real d_k1km = d_kkm * a_km ;//equation 19, d^{k+1}_{k, m}(beta) for t + const Real d_k1kmN = d_kkmN * a_kmN;//equation 19, d^{k+1}_{k, m}(beta) for -t + pKM[(k+1) * 2 + 0] = d_k1km * sign ; + pMK[(k+1) * 2 + 0] = d_k1km * signN;//symmetry from eq 9 + pKM[(k+1) * 2 + 1] = d_k1kmN * sign ; + pMK[(k+1) * 2 + 1] = d_k1kmN * signN;//symmetry from eq 9 + + if(k+2 < jMax) {//if j == k + 1 we're done + //recursively compute by degree to j + Real d_ikm; + Real d_i2km = d_kkm ; + Real d_i1km = d_k1km; + Real d_ikmN; + Real d_i2kmN = d_kkmN ; + Real d_i1kmN = d_k1kmN; + + for(size_t i = k + 2; i < jMax; i++) { + d_ikm = a_jkmFunc (i, k, m, t0) * d_i1km - b_jkm(i, k, m) * d_i2km ;//equation 10, d^i_{k, m}(beta) + d_i2km = d_i1km; + d_i1km = d_ikm ; + d_ikmN = a_jkmFuncN(i, k, m, tN) * d_i1kmN - b_jkm(i, k, m) * d_i2kmN;//equation 10, d^i_{k, m}(beta) + d_i2kmN = d_i1kmN; + d_i1kmN = d_ikmN ; + pKM[i * 2 + 0] = d_i1km * sign ; + pMK[i * 2 + 0] = d_i1km * signN; + pKM[i * 2 + 1] = d_i1kmN * sign ; + pMK[i * 2 + 1] = d_i1kmN * signN; + } + } + } + + //increment table pointers + pKM += jMax * 2; + pMK += jMax * jMax * 2; + } + + //increment table pointers + pK += jMax * jMax * 2; + } + } + + //@brief : compute a table of Wigner (lowercase) d functions at 0 <= beta <= pi [with precomputed coefficient tables] + //@param jMax : maximum order to compute table for + //@param t : cos(beta) + //@param table: location to write d^j_{k,m}(beta) for all non negative j, k, m (must have space for jMax * jMax * jMax * 2 Reals) + //@param pE : precomputed table of e_km + //@param pW : precomputed table of w_jkm + //@param pB : precomputed table of b_jkm + //@note : the table has unused (an uninitialized space) for j < max(|k|, |m|) + //@note : d^j_{k,m}( beta) at table[(k * jMax * jMax + m * jMax + j)*2 + 0] + //@note : d^j_{k,m}(pi-beta) at table[(k * jMax * jMax + m * jMax + j)*2 + 1] + //@note : negative k/m values can be found with the following symmetry relationships + // d^j_{-k,-m}( beta) = (-1)^(k-m) d^j_{k,m}( beta) + // d^j_{ k,-m}( beta) = (-1)^(j+k) d^j_{k,m}(pi - beta) + // d^j_{-k, m}( beta) = (-1)^(j+m) d^j_{k,m}(pi - beta) + template + void dTablePre(const size_t jMax, const Real t, const bool nB, Real * const table, Real const * const pE, Real const * const pW, Real const * const pB) { + //determine which branch of function is needed and compute cos/sin of half angles + const bool isType0 = !std::signbit(t);//is this type 0 or type 2? (type 1 will be grouped with type 0) + const Real tc = Real(1) - t; + const Real tcN = Real(1) + t;//tc for -t + const Real c2 = std::sqrt(tcN / 2);//cos(acos(t)) == cos(beta / 2), always positive since at this point 0 <= beta <= pi + const Real s2 = std::sqrt(tc / 2);//sin(acos(t)) == sin(beta / 2), always positive since at this point 0 <= beta <= pi + + //get function pointers to avoid repeated branch in loop + Real (* const a_kmFunc )( const int64_t, const int64_t, const Real) = isType0 ? a_km_0 : a_km_2 ; + Real (* const a_kmFuncN )( const int64_t, const int64_t, const Real) = isType0 ? a_km_2 : a_km_0 ; + Real (* const a_jkmFunc )(const Real, const int64_t, const int64_t, const int64_t, const Real) = isType0 ? a_jkm_0_pre : a_jkm_2_pre; + Real (* const a_jkmFuncN)(const Real, const int64_t, const int64_t, const int64_t, const Real) = isType0 ? a_jkm_2_pre : a_jkm_0_pre; + const Real t0 = isType0 ? tc : t ; + const Real tN = isType0 ? -t : tcN; + + //precompute integer powers of c2 and s2 + std::vector work(jMax * 4); + Real* const pc2 = work.data(); + Real* const ps2 = work.data() + jMax * 2; + for(size_t i = 0; i < jMax * 2; i++) { + pc2[i] = std::pow(c2, Real(i)); + ps2[i] = std::pow(s2, Real(i)); + } + + //fill table + Real* pK = table;//table + k * jMax * jMAx * 2 + for(size_t k = 0; k < jMax; k++) { + Real* pKM = pK ;//table + k * jMax * jMax * 2 + m * jMax * 2 + Real* pMK = table + k * jMax * 2;//table + m * jMax * jMax * 2 + k * jMax * 2 + for(size_t m = 0; m <= k; m++) { + //determine sign change for swapping k/m + int sign = 1; + int signN = (k-m) % 2 == 0 ? 1 : -1;//symmetry from eq 9 + if(nB) std::swap(sign, signN); + + //compute powers of cos/sin of beta / 2 + const Real cn = pc2[k+m];//std::pow(c2, k+m);//equation 20 for n = k+m for t + const Real sn = ps2[k-m];//std::pow(s2, k-m);//equation 20 for n = k-m for t + const Real cnN = ps2[k+m];//std::pow(s2, k+m);//equation 20 for n = k+m for -t + const Real snN = pc2[k-m];//std::pow(c2, k-m);//equation 20 for n = k-m for -t + + //compute first term for three term recursion + const Real& ekm = pE[k * jMax + m];//e_km(k, m) + const Real d_kkm = cn * sn * ekm;//equation 18, d^k_{k, m}(beta) for t + const Real d_kkmN = cnN * snN * ekm;//equation 18, d^k_{k, m}(beta) for -t + pKM[k * 2 + 0] = d_kkm * sign ; + pMK[k * 2 + 0] = d_kkm * signN;//symmetry from eq 9 + pKM[k * 2 + 1] = d_kkmN * sign ; + pMK[k * 2 + 1] = d_kkmN * signN;//symmetry from eq 9 + + if(k+1 < jMax) {//if j == k we're done + //compute second term for three term recursion + const Real a_km = a_kmFunc (k, m, t0); + const Real a_kmN = a_kmFuncN(k, m, tN); + const Real d_k1km = d_kkm * a_km ;//equation 19, d^{k+1}_{k, m}(beta) for t + const Real d_k1kmN = d_kkmN * a_kmN;//equation 19, d^{k+1}_{k, m}(beta) for -t + pKM[(k+1) * 2 + 0] = d_k1km * sign ; + pMK[(k+1) * 2 + 0] = d_k1km * signN;//symmetry from eq 9 + pKM[(k+1) * 2 + 1] = d_k1kmN * sign ; + pMK[(k+1) * 2 + 1] = d_k1kmN * signN;//symmetry from eq 9 + + if(k+2 < jMax) {//if j == k + 1 we're done + //recursively compute by degree to j + Real d_ikm; + Real d_i2km = d_kkm ; + Real d_i1km = d_k1km; + Real d_ikmN; + Real d_i2kmN = d_kkmN ; + Real d_i1kmN = d_k1kmN; + + for(size_t i = k + 2; i < jMax; i++) { + const size_t idx = k * jMax * jMax + m * jMax + i; + d_ikm = a_jkmFunc (pW[idx], i, k, m, t0) * d_i1km - pB[idx] * d_i2km ;//equation 10, d^i_{k, m}(beta), precomputed (a/b)_jkm(i, k, m) + d_i2km = d_i1km; + d_i1km = d_ikm ; + d_ikmN = a_jkmFuncN(pW[idx], i, k, m, tN) * d_i1kmN - pB[idx] * d_i2kmN;//equation 10, d^i_{k, m}(beta), precomputed (a/b)_jkm(i, k, m) + d_i2kmN = d_i1kmN; + d_i1kmN = d_ikmN ; + pKM[i * 2 + 0] = d_i1km * sign ; + pMK[i * 2 + 0] = d_i1km * signN; + pKM[i * 2 + 1] = d_i1kmN * sign ; + pMK[i * 2 + 1] = d_i1kmN * signN; + } + } + } + + //increment table pointers + pKM += jMax * 2; + pMK += jMax * jMax * 2; + } + + //increment table pointers + pK += jMax * jMax * 2; + } + } + + //@brief : fill precomputed factor tables for dTablePre + //@param jMax : maximum order to compute table for + //@param pE : location to write precomputed table of e_km + //@param pW : location to write precomputed table of w_jkm + //@param pB : location to write precomputed table of b_jkm + template + void dTablePreBuild(const size_t jMax, Real * const pE, Real * const pW, Real * const pB) { + //fill table + for(size_t k = 0; k < jMax; k++) { + for(size_t m = 0; m <= k; m++) { + pE[k * jMax + m] = e_km(k, m); + for(size_t i = k + 2; i < jMax; i++) { + const size_t idx = k * jMax * jMax + m * jMax + i; + pW[idx] = w_jkm(i, k, m); + pB[idx] = b_jkm(i, k, m); + } + } + } + } + + //@brief : compute a table of Wigner (lowercase) d functions at pi/2 + //@param jMax : max j + //@param table: location to write d^j_{k,m}(\frac{\pi}{2}) for j = [0,jMax), k = [0,jMax), m = [0, jMax) (jMax^3 values) + //@param trans: true/false to transpose k/m indices + //@note : d^j_{k,m} located at k * jMax * jMax + m * jMax + j (trans = false) + //@note : d^j_{k,m} located at m * jMax * jMax + k * jMax + j (trans = true ) + template void dTable(const size_t jMax, Real * const table, const bool trans) { + //naive implementation (has a bunch of redundant calculations) + /* + for(int k = 0; k < jMax; k++) { + for(int m = 0; m < jMax; m++) { + for(int j = 0; j < jMax; j++) { + table[k * jMax * jMax + m * jMax + j] = d(j, k, m, 0, false);//k and am swapped for trans = true + } + } + } + */ + + //recursively compute wigner d function values for all d^j_{k,m} where k >= m >= 0 + //use symmetry to fill in table for m < 0 and m > k: /d^j_{ m, k} = (-1)^( k- m) d^j_{k,m} + for(int k = 0; k < jMax; k++) { + for(int m = 0; m <= k; m++) { + const bool km = (k-m) % 2 == 0; + + //compute d^k_{k,m} with closed form solution + const Real d_kkm = std::pow(Real(2), -k) * e_km(k, m);//d^{k }_{k,m} + const Real d_kmk = km ? d_kkm : -d_kkm;//d^{k }_{m,k} + if(trans) { + table[m * jMax * jMax + k * jMax + k ] = d_kkm;//save d^{k }_{k,m} + table[k * jMax * jMax + m * jMax + k ] = d_kmk;//save d^{k }_{m,k} + } else { + table[k * jMax * jMax + m * jMax + k ] = d_kkm;//save d^{k }_{k,m} + table[m * jMax * jMax + k * jMax + k ] = d_kmk;//save d^{k }_{m,k} + } + + if(k + 1 == jMax) continue;//we don't need any higher order terms + + //compute d^{k+1}_{k,m} with recursion + const Real d_k1km = d_kkm * a_km_1(k, m);//d^{k+1}_{k,m} + const Real d_k1mk = km ? d_k1km : -d_k1km;//d^{k+1}_{m,k} + if(trans) { + table[m * jMax * jMax + k * jMax + k + 1] = d_k1km;//save d^{k+1}_{k,m} + table[k * jMax * jMax + m * jMax + k + 1] = d_k1mk;//save d^{k+1}_{m,k} + } else { + table[k * jMax * jMax + m * jMax + k + 1] = d_k1km;//save d^{k+1}_{k,m} + table[m * jMax * jMax + k * jMax + k + 1] = d_k1mk;//save d^{k+1}_{m,k} + } + if(k + 2 == jMax) continue;//we don't need any higher order terms + + //compute higher order terms with 3 term recursion + Real djkm; //d^{j }_{k,m} + Real d_j1km = d_k1km;//d^{j-1}_{k,m} + Real d_j2km = d_kkm ;//d^{j-2}_{k,m} + for(int j = k + 2; j < jMax; j++) { + djkm = a_jkm_1(j, k, m) * d_j1km - b_jkm(j, k, m) * d_j2km;//d^{j}_{k,m} + const Real djak = km ? djkm : -djkm;//d^{j}_{m,k} + if(trans) { + table[m * jMax * jMax + k * jMax + j] = djkm;//save d^{j}_{k,m} + table[k * jMax * jMax + m * jMax + j] = djak;//save d^{j}_{m,k} + } else { + table[k * jMax * jMax + m * jMax + j] = djkm;//save d^{j}_{k,m} + table[m * jMax * jMax + k * jMax + j] = djak;//save d^{j}_{m,k} + } + d_j2km = d_j1km; + d_j1km = djkm ; + } + } + } + } + + //@brief : rotate the spherical harmonic transformation of a real function + //@param bw : bandwidth of harmonic coefficients (max l exclusive) + //@param alm: spherical harmonic coefficients to rotate with \hat{a}^l_{m} at alm[m * bw + j] + //@param blm: location to write rotated spherical harmonic coefficients with \hat{b}^l_{m} at blm[m * bw + j] + //@param zyz: rotation to apply as zyz euler angles + //@note : b^l_m = \sum_{n=-1}^l a^l_n D^l_{m,n}(qu) + template + void rotateHarmonics(const size_t bw, std::complex const * const alm, std::complex * const blm, Real const * const zyz) { + //clear output array + std::vector< std::complex > almRot(bw * bw, std::complex(0));//working array in case alm == blm + + //construct wigner (lowercase) d lookup table for beta + std::vector dBeta(bw * bw * bw * 2); + wigner::dTable(bw, std::cos(zyz[1]), std::signbit(zyz[1]), dBeta.data());//compute wigner (lowercase) d(beta) once + + //now that we can easily compute D^l_{m,n}(qu) do the actual summation + for(size_t m = 0; m < bw; m++) { + const std::complex expAlpha(std::cos(zyz[2] * m), std::sin(zyz[2] * m));//exp(I m gamma) + for(size_t n = 0; n < bw; n++) { + const std::complex expGamma (std::cos(zyz[0] * n), std::sin(zyz[0] * n));//exp(I n alpha) + for(size_t j = std::max(m, n); j < bw; j++) { + const std::complex alGamma = alm[n * bw + j] * expGamma;//\hat{a}^l_{n} + const Real rr = expAlpha.real() * alGamma.real();//ac + const Real ri = expAlpha.real() * alGamma.imag();//ad + const Real ir = expAlpha.imag() * alGamma.real();//bc + const Real ii = expAlpha.imag() * alGamma.imag();//bd + const std::complex vp(rr - ii, ir + ri);//expAlpha * alGamma = \hat{a}^l_{+n} * exp(I m gamma) * exp(I +n alpha) + const std::complex vc(rr + ii, ir - ri);//expAlpha * conj(alGamma) = \hat{a}^l_{-n} * exp(I m gamma) * exp(I -n alpha) * (-1)^n using symmetry of real SHT + const Real& dmn0 = dBeta[(m * bw * bw + n * bw + j) * 2 + 0];//d^j_{m,n}( beta) + const Real& dmn1 = dBeta[(m * bw * bw + n * bw + j) * 2 + 1];//d^j_{m,n}(pi - beta) since d^j_{m,-n}( beta) = (-1)^(j+m) d^j_{m,n}(pi - beta) + almRot[m * bw + j] += vp * dmn0;//\hat{a}^l_{+n} * D^l_{m,+n}(zyz) + if(n > 0) almRot[m * bw + j] += vc * (dmn1 * Real(0 == (j+m+n) % 2 ? 1 : -1));//\hat{a}^l_{-n} * D^l_{m,-n}(zyz) + } + } + } + std::copy(almRot.begin(), almRot.end(), blm); + } + + //@brief : compute first derivative of Wigner (lowercase) d function (also called reduced wigner d) with respect to beta + //@param j : degree in (d/dBeta) d^j_{k,m}(beta) + //@param k : first order in (d/dBeta) d^j_{k,m}(beta) + //@param m : second order in (d/dBeta) d^j_{k,m}(beta) + //@param t : cos(beta) + //@param nB: true/false for negative/positive beta + //@return : (d/dBeta) d^j_{k,m}(beta) + //@note : NAN when j < max(|k|, |m|) + //@note : equivalent to the mathematica function D[WignerD[{j, k, m}, beta], beta] + //@note : negative k/m values have the following symmetry relationships + // dPrime^j_{-k,-m}( beta) = (-1)^(k+m ) dPrime^j_{k,m}( beta) + // dPrime^j_{ k,-m}( beta) = (-1)^(j+k+1) dPrime^j_{k,m}(pi - beta) + // dPrime^j_{-k, m}( beta) = (-1)^(j+m+1) dPrime^j_{k,m}(pi - beta) + template Real dPrime(const int64_t j, const int64_t k, const int64_t m, const Real t, const bool nB) { + //compute prefactor (same for all j, k, and m for a given beta) + const Real csc = Real(1) / std::sqrt(Real(1) - t * t) * (nB ? -1 : 1);//csc(beta), cot(beta) is csc * t + + //compute derivative + const Real d0Term = d(j, k , m, t, nB) * (t * k - m) * csc;//d^j_{k,m}(beta) * (k*cot(beta) - m*csc(beta)) + const Real d1Term = j == k ? 0 : d(j, k+1, m, t, nB) * std::sqrt( Real( (j - k) * (j + k + 1) ) );//d^j_{k+1,m}(beta) * (j+k+1) * sqrt( (j-k) / (j+kl+1) ) + return d0Term - d1Term; + } + + //@brief : compute second derivative of Wigner (lowercase) d function (also called reduced wigner d) with respect to beta + //@param j : degree in (d/dBeta)^2 d^j_{k,m}(beta) + //@param k : first order in (d/dBeta)^2 d^j_{k,m}(beta) + //@param m : second order in (d/dBeta)^2 d^j_{k,m}(beta) + //@param t : cos(beta) + //@param nB: true/false for negative/positive beta + //@return : (d/dBeta)^2 d^j_{k,m}(beta) + //@note : NAN when j < max(|k|, |m|) + //@note : equivalent to the mathematica function D[WignerD[{j, k, m}, beta], {beta, 2}] + //@note : negative k/m values have the following symmetry relationships + // dPrime2^j_{-k,-m}( beta) = (-1)^(k+m) dPrime2^j_{k,m}( beta) + // dPrime2^j_{ k,-m}( beta) = (-1)^(j+k) dPrime2^j_{k,m}(pi - beta) + // dPrime2^j_{-k, m}( beta) = (-1)^(j+m) dPrime2^j_{k,m}(pi - beta) + template Real dPrime2(const int64_t j, const int64_t k, const int64_t m, const Real t, const bool nB) { + //compute prefactor (same for all j, k, and m for a given beta) + const Real csc = Real(1) / std::sqrt(Real(1) - t * t) * (nB ? -1 : 1);//csc(beta), cot(beta) is csc * t + + //compute derivative prefactors + const Real rjk = std::sqrt( Real( (j - k ) * (j + k + 1) ) ); + const Real d0Coef = ( t * t * k * k + t * m * (1 - 2 * k) + (m * m - k) ) * csc * csc; + const Real d1Coef = rjk * (t * (1 + 2 * k) - 2 * m) * csc; + const Real d2Coef = rjk * std::sqrt( Real( (j - k - 1) * (j + k + 2) ) ); + + //compute derivative + const Real d0Term = d(j, k , m, t, nB) * d0Coef; + const Real d1Term = k >= j ? 0 : d(j, k+1, m, t, nB) * d1Coef; + const Real d2Term = k+1 >= j ? 0 : d(j, k+2, m, t, nB) * d2Coef; + return d0Term - d1Term + d2Term; + } + } + +} + +#endif//_WIGNER_H_ diff --git a/include/util/ahe.hpp b/include/util/ahe.hpp new file mode 100644 index 0000000..fa1261b --- /dev/null +++ b/include/util/ahe.hpp @@ -0,0 +1,275 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _AHE_H_ +#define _AHE_H_ + +#include +#include + +//@brief: helper to repeatedly do AHE for the same image size / tiles +//@note : this should theorhetically work for e.g. uint16_t or uint32_t, but be careful since a single 16 bit CDF is 512 kB and a single 32bit CDF is 32 GB +template +class AdaptiveHistogramEqualizer { + static_assert(std::is_floating_point::value, "Real must be floating point type"); + static_assert(std::is_integral::value, "TPix must be unsigned integer type"); + static_assert(std::is_unsigned::value, "TPix must be unsigned integer type"); + static_assert(std::numeric_limits::max() / std::numeric_limits::max() > 64, "size_t not large enough to hold 64 TPix histograms"); + + static const size_t HistBins = size_t(std::numeric_limits::max()) + 1;//number of histogram bins + + //@brief: helper to hold tile bounds + struct TileBounds { + size_t iS, iE;//x start and end + size_t jS, jE;//y start and end + }; + + //@brief: helper to hold pixel interpolation values + struct InterpPair { + size_t l, u;//lower and upper bounding tile indices + Real c, f;//interpolation weights (sum to 1) + }; + + //these values are read only for equalization (can be shared across threads) + std::vector< TileBounds > tiles;//bounds of tiles + std::vector< InterpPair > jInds, iInds;//interpolation coefficients for each row/column + + //these members are working space for equalization (can't be shared across threads) + std::vector cdfs; + + //@brief : compute the patch histograms + //@param im : image to compute histograms from (width and height are assumed to match prior call to setSize) + //@param msk: mask of valid pixels (1/0 for valid/invalid) or NULL for all valid pixels + void computeHist(TPix const* im, char const * const msk = NULL); + + public: + //@brief : set the image size and tile count for the equalizer + //@param w : width of image in pixels + //@param h : height of image in pixels + //@param nx: number of x grid points + //@param ny: number of y grid points + //@note : equalization complexity will scale as nx * ny for high grid densities + void setSize(const size_t w, const size_t h, const size_t nx, const size_t ny); + + //@brief : equalize an image in place using previously set conditions + //@param im : image (width and height are assumed to match prior call to setSize) + //@param msk: mask of valid pixels (1/0 for valid/invalid) or NULL for all valid pixels + void equalize(TPix* im, char const * const msk = NULL); + + //@brief : equalize an image out of place using previously set conditions + //@param im : image (width and height are assumed to match prior call to setSize) + //@param buf: location to write equalized image + //@param msk: mask of valid pixels (1/0 for valid/invalid) or NULL for all valid pixels + void equalize(TPix const * im, Real * buf, char const * const msk = NULL); +}; + +//@brief : apply adaptive histogram equalization to a single image +//@param im: image to equalize +//@param w : width of image in pixels +//@param h : height of image in pixels +//@param nx: number of x grid points +//@param ny: number of y grid points +//@note : this is a convenience function to build and use an AdaptiveHistogramEqualizer object so the performance won't be great +void adHistEq(uint8_t* im, const size_t w, const size_t h, const size_t nx, const size_t ny); + +#include +#include +#include + +//@brief : set the image size and tile count for the equalizer +//@param w : width of image in pixels +//@param h : height of image in pixels +//@param nx: number of x grid points +//@param ny: number of y grid points +//@note : equalization complexity will scale as nx * ny for high grid densities +template +void AdaptiveHistogramEqualizer::setSize(const size_t w, const size_t h, const size_t nx, const size_t ny) { + tiles.resize(nx * ny); + cdfs .resize(nx * ny * HistBins); + + //compute tile bounds + const Real tx = Real(w) / nx;//compute tile width in fractional pixels + const Real ty = Real(h) / ny;//compute tile height in fractional tiles + const Real hWdth = Real(0.5);//histogram calculation area, 1.0 for 50% overlap, 0.5 for mosaic (1.0 does background removal better, 0.5 does enhancement better if there isn't a background) + std::vector jMids(ny), iMids(nx);//tile midpoints + for(size_t j = 0; j < ny; j++) {//loop over tile rows + //compute top, middle, and bottom of tile in pixels + Real midY = ty * j + ty / 2; + Real minY = std::max(midY - ty * hWdth, Real(0)); + Real maxY = std::min(midY + ty * hWdth, Real(h)); + minY = std::round(minY); midY = std::round(midY); maxY = std::round(maxY); + jMids[j] = (size_t)midY; + for(size_t i = 0; i < nx; i++) {//loop over tile cols + //compute left, middle, and right of tile in pixels + Real midX = tx * i + tx / 2; + Real minX = std::max(midX - tx * hWdth, Real(0)); + Real maxX = std::min(midX + tx * hWdth, Real(w)); + minX = std::round(minX); midX = std::round(midX); maxX = std::round(maxX); + if(j == 0) iMids[i] = (size_t)midX; + + //save tile bounds + TileBounds& t = tiles[j * nx + i]; + t.iS = (size_t)minX; t.iE = (size_t)maxX; + t.jS = (size_t)minY; t.jE = (size_t)maxY; + } + } + + //compute y interpolation once + jInds.resize(w); + for(size_t j = 0; j < h; j++) { + const size_t u = std::distance(jMids.cbegin(), std::upper_bound(jMids.cbegin(), jMids.cend(), j)); + if(jMids.size() == u) {//beyond last grid point + jInds[j].l = jInds[j].u = jMids.size() - 1; + jInds[j].c = jInds[j].f = Real(0.5); + } else if(0 == u) {//before first grid point + jInds[j].l = jInds[j].u = 0; + jInds[j].c = jInds[j].f = Real(0.5); + } else {//between 2 grid points, linear interpolate + jInds[j].l = u - 1; + jInds[j].u = u; + jInds[j].f = Real(j - jMids[u - 1]) / (jMids[u] - jMids[u - 1]); + jInds[j].c = Real(1) - jInds[j].f; + } + jInds[j].l *= nx * HistBins;//convert from tile to vectorized histograms index + jInds[j].u *= nx * HistBins;//convert from tile to vectorized histograms index + } + + //compute x interpolation once + iInds.resize(h); + for(size_t i = 0; i < w; i++) { + const size_t u = std::distance(iMids.cbegin(), std::upper_bound(iMids.cbegin(), iMids.cend(), i)); + if(iMids.size() == u) {//beyond last grid point + iInds[i].l = iInds[i].u = iMids.size() - 1; + iInds[i].c = iInds[i].f = Real(0.5); + } else if(0 == u) {//before first grid point + iInds[i].l = iInds[i].u = 0; + iInds[i].c = iInds[i].f = Real(0.5); + } else {//between 2 grid points, linear interpolate + iInds[i].l = u - 1; + iInds[i].u = u; + iInds[i].f = Real(i - iMids[u - 1]) / (iMids[u] - iMids[u - 1]); + iInds[i].c = Real(1) - iInds[i].f; + } + iInds[i].l *= HistBins;//convert from tile to vectorized histograms index + iInds[i].u *= HistBins;//convert from tile to vectorized histograms index + } +} + +//@brief : compute the patch histograms +//@param im : image to compute histograms from (width and height are assumed to match prior call to setSize) +//@param msk: mask of valid pixels (1/0 for valid/invalid) or NULL for all valid pixels +template +void AdaptiveHistogramEqualizer::computeHist(TPix const* im, char const * const msk) { + //compute CDF of each tile up front + size_t hist[HistBins]; + for(size_t i = 0; i < tiles.size(); i++) { + //compute histogram + TileBounds& t = tiles[i]; + std::fill(hist, hist + HistBins, 0); + if(NULL == msk) { + for(size_t j = t.jS; j < t.jE; j++) {//loop over image rows of tile t + for(size_t i = t.iS; i < t.iE; i++) ++hist[im[iInds.size() * j + i]];//loop over image columns of tile t + } + } else { + for(size_t j = t.jS; j < t.jE; j++) {//loop over image rows of tile t + for(size_t i = t.iS; i < t.iE; i++) { + const size_t idx = iInds.size() * j + i; + if(1 == msk[idx]) ++hist[im[idx]];//loop over image columns of tile t + } + } + if(0 == *std::max_element(hist, hist + HistBins)) {//there were no good pixels + std::fill(hist, hist + HistBins, 1);//fill with linear ramp (no adjustment) + } + } + + //convert to normalized cumulative distribution + std::partial_sum(hist, hist + HistBins, hist);//unnormalized CDF + const Real nrm = Real(HistBins-1) / hist[HistBins-1];//normalization so integral of CDF is pixel max + std::transform(hist, hist + HistBins, cdfs.begin() + i * HistBins, [nrm](const size_t& v){return nrm * v;}); + } +} + +//@brief : equalize an image using previously set conditions +//@param im : image (width and height are assumed to match prior call to setSize) +//@param msk: mask of valid pixels (1/0 for valid/invalid) or NULL for all valid pixels +template +void AdaptiveHistogramEqualizer::equalize(TPix* im, char const * const msk) { + computeHist(im, msk); + + //loop over pixels equalizing + for(const InterpPair& j : jInds) { + for(const InterpPair& i : iInds) { + const TPix v = *im; + *im++ = (TPix) ( cdfs[(j.l + i.l) + v] * j.c * i.c + + cdfs[(j.l + i.u) + v] * j.c * i.f + + cdfs[(j.u + i.l) + v] * j.f * i.c + + cdfs[(j.u + i.u) + v] * j.f * i.f + + Real(0.5));//add 0.5 so that casting to uint is rounding + } + } +} + +//@brief : equalize an image using previously set conditions +//@param im : image (width and height are assumed to match prior call to setSize) +//@param msk: mask of valid pixels (1/0 for valid/invalid) or NULL for all valid pixels +template +void AdaptiveHistogramEqualizer::equalize(TPix const * im, Real * buf, char const * const msk) { + computeHist(im, msk); + + //loop over pixels equalizing + for(const InterpPair& j : jInds) { + for(const InterpPair& i : iInds) { + const TPix v = *im++; + *buf++ = cdfs[(j.l + i.l) + v] * j.c * i.c + + cdfs[(j.l + i.u) + v] * j.c * i.f + + cdfs[(j.u + i.l) + v] * j.f * i.c + + cdfs[(j.u + i.u) + v] * j.f * i.f; + } + } +} + +//@brief : apply adaptive histogram equalization to a single image +//@param im: image to equalize +//@param w : width of image in pixels +//@param h : height of image in pixels +//@param nx: number of x grid points +//@param ny: number of y grid points +//@note : this is a convenience function to build and use an AdaptiveHistogramEqualizer object so the performance won't be great +void adHistEq(uint8_t* im, const size_t w, const size_t h, const size_t nx, const size_t ny) { + AdaptiveHistogramEqualizer eq; + eq.setSize(w, h, nx, ny); + eq.equalize(im); +} + +#endif//_AHE_H_ diff --git a/include/util/base64.hpp b/include/util/base64.hpp new file mode 100644 index 0000000..509a53b --- /dev/null +++ b/include/util/base64.hpp @@ -0,0 +1,195 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include +#include + +namespace base64 { + + //@brief : base64 encode some data + //@param ptr : data to base64 encode + //@param count: number of bytes to encode + //@param os : location to write encoded data + //@return : bytes written to os + size_t encode(char const * ptr, size_t count, std::ostream& os) { + static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + for(size_t i = 0; i < count; i+=3) { + //get next 3 bytes + char b[3] = { + ptr[i ] , + (i+1 < count) ? ptr[i+1] : char(0),//dont go out of bounds + (i+2 < count) ? ptr[i+2] : char(0),//dont go out of bounds + }; + + //turn into 4 base64 indices + int c[4] = { + ((b[0] & 0xFC) >> 2), + ((b[0] & 0x03) << 4) | ((b[1] & 0xF0) >> 4), + ((b[1] & 0x0F) << 2) | ((b[2] & 0xC0) >> 6), + ((b[2] & 0x3F) << 0) + }; + + //write to output + os.put(b64[c[0]]); + os.put(b64[c[1]]); + if(i+3 < count || i+3 == count) { + os.put(b64[c[2]]); + os.put(b64[c[3]]); + } else if(i+1 < count) { + os.put(b64[c[2]]); + os.put('='); + } else { + os.put('='); + os.put('='); + } + } + return 4 * ((count + 2) / 3);//number of output bytes + } + + //@brief : base64 decode some data + //@param ptr : data to base64 decode + //@param count: number of bytes to decode + //@param os : location to write encoded data + //@return : bytes written to os + size_t decode(char const * ptr, size_t count, std::ostream& os) { + if(0 != count % 4) throw std::runtime_error("base64 encoded data must be multiple of 4 bytes long (pad with '=' if too short)"); + const size_t maxBytes = (count / 4) * 3;//this is the number of encoded bytes if there was no padding required + for(size_t i = 0; i < count; i+=4) {//loop over 4 character chungs (24 bits encoded as 32 bits) + //extract the next block of characters + const char block[4] = {ptr[i], ptr[i+1], ptr[i+2], ptr[i+3]}; + + //convert characters to indices + char c[4]; + for(size_t j = 0; j < 4; j++) { + //this could probably use a lookup table but that might be wrong (if the compiler isn't ascii, admittedly unlikely) + switch(block[j]) { + case 'A': c[j] = 0x00; break; + case 'B': c[j] = 0x01; break; + case 'C': c[j] = 0x02; break; + case 'D': c[j] = 0x03; break; + case 'E': c[j] = 0x04; break; + case 'F': c[j] = 0x05; break; + case 'G': c[j] = 0x06; break; + case 'H': c[j] = 0x07; break; + case 'I': c[j] = 0x08; break; + case 'J': c[j] = 0x09; break; + case 'K': c[j] = 0x0A; break; + case 'L': c[j] = 0x0B; break; + case 'M': c[j] = 0x0C; break; + case 'N': c[j] = 0x0D; break; + case 'O': c[j] = 0x0E; break; + case 'P': c[j] = 0x0F; break; + case 'Q': c[j] = 0x10; break; + case 'R': c[j] = 0x11; break; + case 'S': c[j] = 0x12; break; + case 'T': c[j] = 0x13; break; + case 'U': c[j] = 0x14; break; + case 'V': c[j] = 0x15; break; + case 'W': c[j] = 0x16; break; + case 'X': c[j] = 0x17; break; + case 'Y': c[j] = 0x18; break; + case 'Z': c[j] = 0x19; break; + case 'a': c[j] = 0x1A; break; + case 'b': c[j] = 0x1B; break; + case 'c': c[j] = 0x1C; break; + case 'd': c[j] = 0x1D; break; + case 'e': c[j] = 0x1E; break; + case 'f': c[j] = 0x1F; break; + case 'g': c[j] = 0x20; break; + case 'h': c[j] = 0x21; break; + case 'i': c[j] = 0x22; break; + case 'j': c[j] = 0x23; break; + case 'k': c[j] = 0x24; break; + case 'l': c[j] = 0x25; break; + case 'm': c[j] = 0x26; break; + case 'n': c[j] = 0x27; break; + case 'o': c[j] = 0x28; break; + case 'p': c[j] = 0x29; break; + case 'q': c[j] = 0x2A; break; + case 'r': c[j] = 0x2B; break; + case 's': c[j] = 0x2C; break; + case 't': c[j] = 0x2D; break; + case 'u': c[j] = 0x2E; break; + case 'v': c[j] = 0x2F; break; + case 'w': c[j] = 0x30; break; + case 'x': c[j] = 0x31; break; + case 'y': c[j] = 0x32; break; + case 'z': c[j] = 0x33; break; + case '0': c[j] = 0x34; break; + case '1': c[j] = 0x35; break; + case '2': c[j] = 0x36; break; + case '3': c[j] = 0x37; break; + case '4': c[j] = 0x38; break; + case '5': c[j] = 0x39; break; + case '6': c[j] = 0x3A; break; + case '7': c[j] = 0x3B; break; + case '8': c[j] = 0x3C; break; + case '9': c[j] = 0x3D; break; + case '+': c[j] = 0x3E; break; + case '/': c[j] = 0x3F; break; + case '=': {//termination + if(i+4 < count || j < 2) throw std::runtime_error("unexpected base64 terminator");//make sure this didn't show up too early + if(2 == j && block[3] != '=') throw std::runtime_error("character after first `='");//make sure we have either ..== or ...= (not ..=.) + c[j] = 0x00;//fill with 0 so we don't disrupt other bytes + } break; + default : throw std::runtime_error(std::string("unexpected base64 character `") + ptr[i] + "'"); + } + } + + //decode and 3 bytes + const int b[3] = { + ((c[0] << 2) & 0xFC ) | ( (c[1] >> 4) & 0x03 ), + ((c[1] << 4) & 0xF0 ) | ( (c[2] >> 2) & 0x0F ), + ((c[2] << 6) & 0xC0 ) | ( (c[3] ) & 0x3F ) + }; + + //save extracted bytes + os.put(b[0]);//we always save the first byte + if('=' == block[3]) {//don't save all bytes + if('=' == block[2]) {//only 1 byte was encoded + return maxBytes - 2; + } else {//only 2 bytes were incoded + os.put(b[1]); + return maxBytes - 1; + } + } else {//this isn't the end or there were not padding bytes + os.put(b[1]); + os.put(b[2]); + } + } + return maxBytes;//for no padding on last block + } +} + diff --git a/include/util/bmp.hpp b/include/util/bmp.hpp new file mode 100644 index 0000000..94effbd --- /dev/null +++ b/include/util/bmp.hpp @@ -0,0 +1,580 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _BMP_H_ +#define _BMP_H_ + +#include +#include + +namespace bmp { + //bitmap info header + struct Header { + //@brief : read info from an istream + //@param is: istream to read from + //@return : is + std::istream& read(std::istream& is); + + //@brief : read info from a raw pointer + //@param ptr: raw buffer to read from + //@return : true/false if read was successful/failed + void read(char const * const ptr); + + //@brief : determine the number of bytes needed to store the image + //@param gry: true to get the number of bytes to write as a grayscale image instead + uint32_t bytes(const bool gry = false) const; + + //@brief : read the image into a raw buffer + //@param is : istream to read from + //@param buf: location to write image + //@param gry: true to convert to grayscale (red channel for color, cast for 1/4 bit) + //@return : is + std::istream& readImage(std::istream& is, char * const buf, const bool gry = false) const; + + //@brief : read the image into a raw buffer + //@param ptr: raw buffer to read from + //@param buf: location to write image + //@param gry: true to convert to grayscale (red channel for color, cast for 1/4 bit) + void readImage(char const * const ptr, char * const buf, const bool gry = false) const; + + //Header + char signature[2];//magic bytes + uint32_t fileSize ;//size of the file in bytes + uint16_t res1, res2 ;//reserved space + uint32_t offset ;//offset to image data + + //Info Header + + // modified BITMAPCOREHEADER (16 bytes) (actual BITMAPCOREHEADER is 12 bytes) + uint32_t size ;// size of this structure in bytes + uint32_t width ;// bitmap width in pixels // only int16 in bitmapcoreheader only + uint32_t height ;// bitmap height in pixels // only int16 in bitmapcoreheader only + uint16_t planes ;// must be 1 + uint16_t bitCount ;// bits per pixel (1, 4, 8, or 24) + + // BITMAPINFOHEADER (40 bytes) adds these fields and extends bitCount to include (0, 16, and 32) + uint32_t compression;// type of compression (0:none,1:8bit rle,2:4bit rle,3:indexed,4:jpg,5:png) + uint32_t sizeImage ;// size of image in bytes + uint32_t xRes ;// x resolution in pixels per meter + uint32_t yRes ;// y resolution in pixels per meter + uint32_t colorsUsed ;// number of lut values used + uint32_t colorsImprt;// number of lut values needed to display the image + + // BITMAPV4HEADER (108 bytes) adds these fields + uint32_t redMask ;// red bits in rgb image + uint32_t greenMask ;// green bits in rgb image + uint32_t blueMask ;// blue bits in rgb image + uint32_t alphaMask ;// alpha bits in rgba image + uint32_t colorSpace ;// color space (flag for if cie endpoints are given) + uint32_t redX ;// x coordinate of red in cie + uint32_t redY ;// y ' ' + uint32_t redZ ;// z ' ' + uint32_t greenX ;// ' + uint32_t greenY ;// green + uint32_t greenZ ;// ' + uint32_t blueX ;// ' + uint32_t blueY ;// blue + uint32_t blueZ ;// ' + uint32_t gammaRed ;// red gamma curve value + uint32_t gammaGreen ;// green gamma curve value + uint32_t gammaBlue ;// blue gamma curve value + + // BITMAPV5HEADER (124 bytes) adds these fields and support for additional colorSpace types + uint32_t intent ;// rendering intent + uint32_t profileData;// offset in bytes from beginning of header to start of profile data + uint32_t profileSize;// size in bytes of profile data + uint32_t reserved ;// should be 0 + + private: + //@brief : read BITMAPCOREHEADER from an istream + //@param is: istream to read from + //@return : is + std::istream& readCore(std::istream& is); + + //@brief : read BITMAPINFOHEADER from an istream + //@param is: istream to read from + //@return : is + std::istream& readInfo(std::istream& is); + + //@brief : read BITMAPV4HEADER from an istream + //@param is: istream to read from + //@return : is + std::istream& readV4(std::istream& is); + + //@brief : read BITMAPV5HEADER from an istream + //@param is: istream to read from + //@return : is + std::istream& readV5(std::istream& is); + + //@brief: byteswap to little endian from big if needed + void headerToLittle(); + + //@brief: byteswap to little endian from big if needed + void infoToLittle(); + }; +} + +struct Bitmap { + //@brief : read a bitmap from a file + //@param fileName: file to read from + //@param gry : true to get the number of bytes to write as a grayscale image instead + void read(std::string fileName); + + bmp::Header header; + std::vector buff ;//data in row major order (each row padded to nearest byte) +}; + +//////////////////////////////////////////////////////////////////////// +// Implementations // +//////////////////////////////////////////////////////////////////////// + +#include + +namespace bmp { + namespace detail { + //@brief : check if the system is big or little endian + //@return: true/false for big/little endian system + bool bigEndian() { + static const union { + uint16_t i ; + char c[2]; + } u = {0x0001}; + static const bool big = 0x00 == u.c[0]; + return big; + } + + //@brief : byteswap fixed size integers + //@param uxx: xx bit unsigned integer to byteswap + //@return : byteswapped integer + uint16_t swapU16(const uint16_t& u16) {return ((u16<< 8)&0xFF00) | ((u16>> 8)&0x00FF);} + uint32_t swapU32(const uint32_t& u32) {return ((u32<<24)&0xFF000000) | ((u32<< 8)&0x00FF0000) | ((u32>> 8)&0x0000FF00) | ((u32>>24)&0x000000FF);} + } + + //////////////////////////////////////////////////////////////////////// + // Header // + //////////////////////////////////////////////////////////////////////// + + //@brief : read info from an istream + //@param is: istream to read from + //@return : is + std::istream& Header::read(std::istream& is) { + //read header + is.read(signature , 2); + is.read((char*)&fileSize, 4); + is.read((char*)&res1 , 2); + is.read((char*)&res2 , 2); + is.read((char*)&offset , 4); + headerToLittle(); + if('B' != signature[0] || 'M' != signature[1]) throw std::runtime_error("not a valid bitmap file"); + + //read size + is.read((char*)&size, 4); + if(detail::bigEndian()) size = detail::swapU32(size); + + //select reader based on header size + switch(size) { + case 12: readCore(is); break;//BITMAPCOREHEADER + case 40: readInfo(is); break;//BITMAPINFOHEADER + case 108: readV4 (is); break;//BITMAPV4HEADER + case 124: readV5 (is); break;//BITMAPV5HEADER + default : throw std::runtime_error("unsupported header size"); + } + infoToLittle(); + return is; + } + + //@brief : read info from a raw pointer + //@param ptr: raw buffer to read from + //@return : true/false if read was successful/failed + void Header::read(char const * const ptr) { + //read header + signature[0] = ptr[0]; + signature[1] = ptr[1]; + std::copy(ptr + 2, ptr + 6, (char*)&fileSize); + std::copy(ptr + 6, ptr + 8, (char*)&res1 ); + std::copy(ptr + 8, ptr + 10, (char*)&res2 ); + std::copy(ptr + 10, ptr + 14, (char*)&offset ); + headerToLittle(); + if(!('B' == signature[0] && 'M' == signature[1])) throw std::runtime_error("not a valid bitmap file"); + + //read size + char const * const pInfo = ptr + 14; + std::copy(pInfo, pInfo + 4, (char*)&size); + if(detail::bigEndian()) size = detail::swapU32(size); + + //read fields based on header size + switch(size) { + case 124://BITMAPV5HEADER + // BITMAPV5HEADER (124 bytes) adds these fields and support for additional colorSpace types + std::copy(pInfo + 108, pInfo + 112, (char*)&intent ); + std::copy(pInfo + 112, pInfo + 116, (char*)&profileData); + std::copy(pInfo + 116, pInfo + 120, (char*)&profileSize); + std::copy(pInfo + 120, pInfo + 124, (char*)&reserved ); + //intentional fall through + + case 108://BITMAPV4HEADER + // BITMAPV4HEADER (108 bytes) adds these fields + std::copy(pInfo + 40, pInfo + 44, (char*)&redMask ); + std::copy(pInfo + 44, pInfo + 48, (char*)&greenMask ); + std::copy(pInfo + 48, pInfo + 52, (char*)&blueMask ); + std::copy(pInfo + 52, pInfo + 56, (char*)&alphaMask ); + std::copy(pInfo + 56, pInfo + 60, (char*)&colorSpace ); + std::copy(pInfo + 60, pInfo + 64, (char*)&redX ); + std::copy(pInfo + 64, pInfo + 68, (char*)&redY ); + std::copy(pInfo + 68, pInfo + 72, (char*)&redZ ); + std::copy(pInfo + 72, pInfo + 76, (char*)&greenX ); + std::copy(pInfo + 76, pInfo + 80, (char*)&greenY ); + std::copy(pInfo + 80, pInfo + 84, (char*)&greenZ ); + std::copy(pInfo + 84, pInfo + 88, (char*)&blueX ); + std::copy(pInfo + 88, pInfo + 92, (char*)&blueY ); + std::copy(pInfo + 92, pInfo + 96, (char*)&blueZ ); + std::copy(pInfo + 96, pInfo + 100, (char*)&gammaRed ); + std::copy(pInfo + 100, pInfo + 104, (char*)&gammaGreen ); + std::copy(pInfo + 104, pInfo + 108, (char*)&gammaBlue ); + //intentional fall through + + case 40://BITMAPINFOHEADER + // BITMAPINFOHEADER (40 bytes) adds these fields and extends bitCount to include (0, 16, and 32) + std::copy(pInfo + 16, pInfo + 20, (char*)&compression); + std::copy(pInfo + 20, pInfo + 24, (char*)&sizeImage ); + std::copy(pInfo + 24, pInfo + 28, (char*)&xRes ); + std::copy(pInfo + 28, pInfo + 32, (char*)&yRes ); + std::copy(pInfo + 32, pInfo + 36, (char*)&colorsUsed ); + std::copy(pInfo + 36, pInfo + 40, (char*)&colorsImprt); + + // modified BITMAPCOREHEADER (16 bytes) (actual BITMAPCOREHEADER is 12 bytes) + std::copy(pInfo + 4, pInfo + 8, (char*)&width ); + std::copy(pInfo + 8, pInfo + 12, (char*)&height ); + std::copy(pInfo + 12, pInfo + 14, (char*)&planes ); + std::copy(pInfo + 14, pInfo + 16, (char*)&bitCount ); + break; + + case 12: {//BITMAPCOREHEADER + uint16_t tmp; + std::copy(pInfo + 4, pInfo + 6, (char*)&tmp ); width = tmp; + std::copy(pInfo + 6, pInfo + 8, (char*)&tmp ); height = tmp; + std::copy(pInfo + 8, pInfo + 10, (char*)&planes ); + std::copy(pInfo + 10, pInfo + 12, (char*)&bitCount ); + } break; + + default: throw std::runtime_error("unsupported header size"); + } + infoToLittle(); + } + + //@brief : determine the number of bytes needed to store the image + //@param gry: true to get the number of bytes to write as a grayscale image instead + uint32_t Header::bytes(const bool gry) const { + if(gry) { + switch(bitCount) { + case 0: return 0; + + case 16: return width * height * 2;//16 bit + + case 1: //bitmask -> 8bit + case 4: //4 bit -> 8 bit + case 8: //already 8 bit + case 24: //rgb -> 8 bit + case 32: return width * height;//rgba -> 8 bit + + default: throw std::runtime_error("unsupported bits per pixel"); + } + } else { + switch(bitCount) { + case 0: return 0; + case 1: return (width * height + 7)/8; + case 4: return (width * height + 1)/2; + case 8: return width * height ; + case 16: return width * height * 2; + case 24: return width * height * 3; + case 32: return width * height * 4; + default: throw std::runtime_error("unsupported bits per pixel"); + } + } + } + + //@brief : read the image into a raw buffer + //@param is : istream to read from + //@param buf: location to write image + //@param gry: true to convert to grayscale (red channel for color, cast for 1/4 bit) + //@return : is + std::istream& Header::readImage(std::istream& is, char * const buf, const bool gry) const { + //make sure we can read this bitmap + if(0 != compression) throw std::runtime_error("unsupported compression"); + //compute row padding + uint32_t rowBytes = width; + switch(bitCount) { + case 0: rowBytes = 0; return is; + case 1: throw std::runtime_error("unsupported bit depth for reading");//these are going to be annoying to implement properly and probably aren't needed + case 4: throw std::runtime_error("unsupported bit depth for reading");//these are going to be annoying to implement properly and probably aren't needed + case 8: break; + case 16: rowBytes *= 2; break; + case 24: rowBytes *= 3; break; + case 32: rowBytes *= 4; break; + default: throw std::runtime_error("unsupported bits per pixel"); + } + const size_t padBytes = (4 - (rowBytes % 4)) % 4;//rows in the file are padded to multiples of 32 bits + const size_t bmpRowBytes = rowBytes + padBytes; + + //read data (if we made it this far we have 8, 16, 24, or 32 bit pixels) + is.seekg(offset); + uint32_t pad; + char * pOut = buf; + if(gry && (bitCount > 16)) { + //read only the red channel + const size_t stride = bitCount / 8; + for(uint32_t j = 0; j < height; j++) { + for(uint32_t i = 0; i < width; i++) { + is.read(pOut + i, 1); + is.read((char*)&pad, stride - 1); + } + is.read((char*)&pad, padBytes); + pOut += width; + } + } else { + //read raw data + for(uint32_t j = 0; j < height; j++) { + is.read(pOut, rowBytes); + is.read((char*)&pad, padBytes); + pOut += rowBytes; + } + } + return is; + } + + //@brief : read the image into a raw buffer + //@param ptr: raw buffer to read from + //@param buf: location to write image + //@param gry: true to convert to grayscale (red channel for color, cast for 1/4 bit) + void Header::readImage(char const * const ptr, char * const buf, const bool gry) const { + //make sure we can read this bitmap + if(0 != compression) throw std::runtime_error("unsupported compression"); + + //compute row padding + uint32_t rowBytes = width; + switch(bitCount) { + case 0: rowBytes = 0; return; + case 1: throw std::runtime_error("unsupported bit depth for reading");//these are going to be annoying to implement properly and probably aren't needed + case 4: throw std::runtime_error("unsupported bit depth for reading");//these are going to be annoying to implement properly and probably aren't needed + case 8: break; + case 16: rowBytes *= 2; break; + case 24: rowBytes *= 3; break; + case 32: rowBytes *= 4; break; + default: throw std::runtime_error("unsupported bits per pixel"); + } + const size_t padBytes = (4 - (rowBytes % 4)) % 4;//rows in the file are padded to multiples of 32 bits + const size_t bmpRowBytes = rowBytes + padBytes; + + //read data (if we made it this far we have 8, 16, 24, or 32 bit pixels) + char const * pIn = ptr + offset; + char * pOut = buf; + if(gry && (bitCount > 16)) { + //read only the red channel + const size_t stride = bitCount / 8; + for(uint32_t j = 0; j < height; j++) { + for(uint32_t i = 0; i < width; i++) pOut[i] = pIn[i * stride]; + pIn += bmpRowBytes; + pOut += width ; + } + } else { + //read raw data + for(uint32_t j = 0; j < height; j++) { + std::copy(pIn, pIn + rowBytes, pOut); + pIn += bmpRowBytes; + pOut += rowBytes; + } + } + } + + //@brief : read BITMAPCOREHEADER from an istream + //@param is: istream to read from + //@return : is + std::istream& Header::readCore(std::istream& is) { + uint16_t tmp = 0; + is.read((char*)&tmp , 2); width = tmp; + is.read((char*)&tmp , 2); height = tmp; + is.read((char*)&planes , 2); + is.read((char*)&bitCount, 2); + if(planes != 1) throw std::runtime_error("bitmap planes must be 1"); + if(!(bitCount == 1 || + bitCount == 4 || + bitCount == 8 || + bitCount == 24 )) throw std::runtime_error("bitmap bitcount must be 1, 4, 8, or 24"); + return is; + } + + //@brief : read BITMAPINFOHEADER from an istream + //@param is: istream to read from + //@return : is + std::istream& Header::readInfo(std::istream& is) { + //read modified core header + is.read((char*)&width , 4); + is.read((char*)&height , 4); + is.read((char*)&planes , 2); + is.read((char*)&bitCount, 2); + if(planes != 1) throw std::runtime_error("bitmap planes must be 1"); + if(!(bitCount == 0 || + bitCount == 1 || + bitCount == 4 || + bitCount == 8 || + bitCount == 16 || + bitCount == 24 || + bitCount == 32 )) throw std::runtime_error("bitmap bitcount must be 1, 4, 8. 16, 24, or 32"); + + //read extra fields + is.read((char*)&compression, 4); + is.read((char*)&sizeImage , 4); + is.read((char*)&xRes , 4); + is.read((char*)&yRes , 4); + is.read((char*)&colorsUsed , 4); + is.read((char*)&colorsImprt, 4); + return is; + } + + //@brief : read BITMAPV4HEADER from an istream + //@param is: istream to read from + //@return : is + std::istream& Header::readV4(std::istream& is) { + //read BITMAPINFOHEADER + readInfo(is); + + //read extra fields + is.read((char*)&redMask , 4); + is.read((char*)&greenMask , 4); + is.read((char*)&blueMask , 4); + is.read((char*)&alphaMask , 4); + is.read((char*)&colorSpace, 4); + is.read((char*)&redX , 4); + is.read((char*)&redY , 4); + is.read((char*)&redZ , 4); + is.read((char*)&greenX , 4); + is.read((char*)&greenY , 4); + is.read((char*)&greenZ , 4); + is.read((char*)&blueX , 4); + is.read((char*)&blueY , 4); + is.read((char*)&blueZ , 4); + is.read((char*)&gammaRed , 4); + is.read((char*)&gammaGreen, 4); + is.read((char*)&gammaBlue , 4); + return is; + } + + //@brief : read BITMAPV5HEADER from an istream + //@param is: istream to read from + //@return : is + std::istream& Header::readV5(std::istream& is) { + //read BITMAPV4HEADER + readInfo(is); + + //read extra fields + is.read((char*)&intent , 4); + is.read((char*)&profileData, 4); + is.read((char*)&profileSize, 4); + is.read((char*)&reserved , 4); + return is; + } + + //@brief: byteswap to little endian from big if needed + void Header::headerToLittle() { + if(detail::bigEndian()) { + fileSize = detail::swapU32(fileSize); + res1 = detail::swapU16(res1 ); + res2 = detail::swapU16(res2 ); + offset = detail::swapU32(offset ); + } + } + + //@brief: byteswap to little endian from big if needed + void Header::infoToLittle() { + if(detail::bigEndian()) { + switch(size) { + case 124://BITMAPV5HEADER + // BITMAPV5HEADER (124 bytes) adds these fields and support for additional colorSpace types + intent = detail::swapU32(intent ); + profileData = detail::swapU32(profileData); + profileSize = detail::swapU32(profileSize); + reserved = detail::swapU32(reserved ); + //intentional fall through + + case 108://BITMAPV4HEADER + // BITMAPV4HEADER (108 bytes) adds these fields + redMask = detail::swapU32(redMask ); + greenMask = detail::swapU32(greenMask ); + blueMask = detail::swapU32(blueMask ); + alphaMask = detail::swapU32(alphaMask ); + colorSpace = detail::swapU32(colorSpace ); + redX = detail::swapU32(redX ); + redY = detail::swapU32(redY ); + redZ = detail::swapU32(redZ ); + greenX = detail::swapU32(greenX ); + greenY = detail::swapU32(greenY ); + greenZ = detail::swapU32(greenZ ); + blueX = detail::swapU32(blueX ); + blueY = detail::swapU32(blueY ); + blueZ = detail::swapU32(blueZ ); + gammaRed = detail::swapU32(gammaRed ); + gammaGreen = detail::swapU32(gammaGreen ); + gammaBlue = detail::swapU32(gammaBlue ); + //intentional fall through + + case 40://BITMAPINFOHEADER + // BITMAPINFOHEADER (40 bytes) adds these fields and extends bitCount to include (0, 16, and 32) + compression = detail::swapU32(compression); + sizeImage = detail::swapU32(sizeImage ); + xRes = detail::swapU32(xRes ); + yRes = detail::swapU32(yRes ); + colorsUsed = detail::swapU32(colorsUsed ); + colorsImprt = detail::swapU32(colorsImprt); + + // modified BITMAPCOREHEADER (16 bytes) (actual BITMAPCOREHEADER is 12 bytes) + width = detail::swapU32(width ); + height = detail::swapU32(height ); + planes = detail::swapU16(planes ); + bitCount = detail::swapU16(bitCount ); + break; + + case 12://BITMAPCOREHEADER + width = detail::swapU16(width ); + height = detail::swapU16(height ); + planes = detail::swapU16(planes ); + bitCount = detail::swapU16(bitCount ); + break; + + default: throw std::runtime_error("unsupported header size"); + } + } + } +} + +#endif//_BMP_H_ diff --git a/include/util/colorspace.hpp b/include/util/colorspace.hpp new file mode 100644 index 0000000..1d4a725 --- /dev/null +++ b/include/util/colorspace.hpp @@ -0,0 +1,494 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + +#ifndef _COLORSPACE_H_ +#define _COLORSPACE_H_ + +namespace color { + //@brief: color space conversion functions abc2ijk where abc/ijk are two of + // -rgb, {r , g , b }: standard red, green, blue (sRGB) + // -xyz, {X , Y , Z }: CIE 1931 XYZ color space ['master' (original) perceptually uniform color space] + // -luv, {L*, u*, v*}: 1976 CIELUV color space [perceptually uniform space for computer displays] + // -lab, {L*, a*, b*}: 1976 CIELab color space [perceptually uniform space for print] + // -hsv, {h , s , v }: hue, saturation, value (cylindrical) + // -hsl, {h , s , l }: hue, saturation, lightness (cylindrical) + //@note: rgb, hsv, and hsl are restricted to the range [0,1] with values outside representing imaginary colors + //@note: the range [0,1] is used for hue in hsv/hsl (not [0,2*pi] or [0,360]) + //@note: all conversions are available using the shortest path in network below + // .->hsv <---> rgb <---> xyz <---> luv + // `->hsl <-' `-> lab + // therefore the following direct transformations are implemented + // -rgb2xyz, rgb2hsv, rgb2hsl + // -xyz2rgb, xyz2luv, xyz2lab + // -luv2xyz, lab2xyz + // -hsv2rgb, hsv2hsl + // -hsl2rgb, hsl2hsv + // with indirect transformations requiring a combination of the above + //@param abc: values to convert from abc color space + //@param ijk: location to write values converted to ijk color space (can be the same as parameter abc) + //@param ill: standard illuminant as xyz (only required for conversions involving xyz<->luv or xyz<->lab, defaults to CIE illuminant D65 for 2 degree observer) + //@return: true/false if the values fall outside the ijk gamut for conversions to that pass through xyz2rgb (void for others) + template void rgb2xyz(T const * const rgb, T * const xyz ); + template void rgb2luv(T const * const rgb, T * const luv, T const * ill = NULL); + template void rgb2lab(T const * const rgb, T * const lab, T const * ill = NULL); + template void rgb2hsv(T const * const rgb, T * const hsv ); + template void rgb2hsl(T const * const rgb, T * const hsl ); + + template bool xyz2rgb(T const * const xyz, T * const rgb ); + template void xyz2luv(T const * const xyz, T * const luv, T const * ill = NULL); + template void xyz2lab(T const * const xyz, T * const lab, T const * ill = NULL); + template bool xyz2hsv(T const * const xyz, T * const hsv ); + template bool xyz2hsl(T const * const xyz, T * const hsl ); + + template bool luv2rgb(T const * const luv, T * const rgb, T const * ill = NULL); + template void luv2xyz(T const * const luv, T * const xyz, T const * ill = NULL); + template void luv2lab(T const * const luv, T * const lab, T const * ill = NULL); + template bool luv2hsv(T const * const luv, T * const hsv, T const * ill = NULL); + template bool luv2hsl(T const * const luv, T * const hsl, T const * ill = NULL); + + template bool lab2rgb(T const * const lab, T * const rgb, T const * ill = NULL); + template void lab2xyz(T const * const lab, T * const xyz, T const * ill = NULL); + template void lab2luv(T const * const lab, T * const luv, T const * ill = NULL); + template bool lab2hsv(T const * const lab, T * const hsv, T const * ill = NULL); + template bool lab2hsl(T const * const lab, T * const hsl, T const * ill = NULL); + + template void hsv2rgb(T const * const hsv, T * const rgb ); + template void hsv2xyz(T const * const hsv, T * const xyz ); + template void hsv2luv(T const * const hsv, T * const luv, T const * ill = NULL); + template void hsv2lab(T const * const hsv, T * const lab, T const * ill = NULL); + template void hsv2hsl(T const * const hsv, T * const hsl ); + + template void hsl2rgb(T const * const hsl, T * const rgb ); + template void hsl2xyz(T const * const hsl, T * const xyz ); + template void hsl2luv(T const * const hsl, T * const luv, T const * ill = NULL); + template void hsl2lab(T const * const hsl, T * const lab, T const * ill = NULL); + template void hsl2hsv(T const * const hsl, T * const hsv ); +} + +//////////////////////////////////////////////////////////////////////// +// Implementation Details // +//////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +namespace color { + namespace detail { + //@brief : invert a 3x3 matrix analytically + //@param mat: matrix to invert in row major order + template std::array inv3x3(const std::array& mat) { + const T det = mat[3*0+0] * mat[3*1+1] * mat[3*2+2] + mat[3*0+1] * mat[3*1+2] * mat[3*2+0] + mat[3*0+2] * mat[3*1+0] * mat[3*2+1] + -(mat[3*0+0] * mat[3*1+2] * mat[3*2+1] + mat[3*0+1] * mat[3*1+0] * mat[3*2+2] + mat[3*0+2] * mat[3*1+1] * mat[3*2+0]); + return std::array { + (mat[3*1+1]*mat[3*2+2] - mat[3*1+2]*mat[3*2+1]) / det, (mat[3*0+2]*mat[3*2+1] - mat[3*0+1]*mat[3*2+2]) / det, (mat[3*0+1]*mat[3*1+2] - mat[3*0+2]*mat[3*1+1]) / det, + (mat[3*1+2]*mat[3*2+0] - mat[3*1+0]*mat[3*2+2]) / det, (mat[3*0+0]*mat[3*2+2] - mat[3*0+2]*mat[3*2+0]) / det, (mat[3*0+2]*mat[3*1+0] - mat[3*0+0]*mat[3*1+2]) / det, + (mat[3*1+0]*mat[3*2+1] - mat[3*1+1]*mat[3*2+0]) / det, (mat[3*0+1]*mat[3*2+0] - mat[3*0+0]*mat[3*2+1]) / det, (mat[3*0+0]*mat[3*1+1] - mat[3*0+1]*mat[3*1+0]) / det + }; + } + + //@brief : compute a matrix to convert from XYZ --> rgb + //@param rgb: chromaticity of {red point, green point, blue point} as XYZ + //@param w : chromaticity of white point as XYZ + template + std::array rgbMat(const T rgb[3][3], T const w[3]) { + //build and invert 3x3 matrices to solve for rows of conversion matrix + //matrix * (r, g, b) = {x, 0, 0}, {0, x, 0}, {0, x, 0} and matrix^-1 * {1,1,1} = w + const T W[3] = {w[0] / w[1], T(1), w[2] / w[1]}; + const std::array invR = inv3x3({ W[0] , W[1] , W[2] , rgb[1][0], rgb[1][1], rgb[1][2], rgb[2][0], rgb[2][1], rgb[2][2]}); + const std::array invG = inv3x3({rgb[0][0], rgb[0][1], rgb[0][2], W[0] , W[1] , W[2] , rgb[2][0], rgb[2][1], rgb[2][2]}); + const std::array invB = inv3x3({rgb[0][0], rgb[0][1], rgb[0][2], rgb[1][0], rgb[1][1], rgb[1][2], W[0] , W[1] , W[2] }); + return {invR[3*0+0], invR[3*1+0], invR[3*2+0], invG[3*0+1], invG[3*1+1], invG[3*2+1], invB[3*0+2], invB[3*1+2], invB[3*2+2]};//assemble matrix + } + + //@brief : convert from hue, chroma, and minimum to rgb + //@param h : hue + //@param c : chroma + //@param m : minimum + //@param rgb: location to write rgb (red, green, blue) values + template void hcm2rgb(const T h, const T c, const T m, T * const rgb) { + const T h6 = h * 6; + const T x = c * (T(1) - std::fabs(std::fmod(h6, T(2)) - T(1))); + switch((size_t)h6) { + case 6://intentional fall through + case 0: rgb[0] = c+m; rgb[1] = x+m; rgb[2] = m; return; + case 1: rgb[0] = x+m; rgb[1] = c+m; rgb[2] = m; return; + case 2: rgb[0] = m; rgb[1] = c+m; rgb[2] = x+m; return; + case 3: rgb[0] = m; rgb[1] = x+m; rgb[2] = c+m; return; + case 4: rgb[0] = x+m; rgb[1] = m; rgb[2] = c+m; return; + case 5: rgb[0] = c+m; rgb[1] = m; rgb[2] = x+m; return; + } + } + + //constants for standard illuminants and common rgb gamuts + template + struct Standards { + //standard illuminants as XYZ + static const T A_2 [3], A_10 [3];//A for 2 and 10 degree observer + static const T B_2 [3], B_10 [3];//B for 2 and 10 degree observer + static const T C_2 [3], C_10 [3];//C for 2 and 10 degree observer + static const T D50_2[3], D50_10[3];//D50 for 2 and 10 degree observer + static const T D55_2[3], D55_10[3];//D55 for 2 and 10 degree observer + static const T D65_2[3], D65_10[3];//D65 for 2 and 10 degree observer + static const T D75_2[3], D75_10[3];//D75 for 2 and 10 degree observer + static const T E [3]; + + //chromaticities of RGB standards ({{red point}, {green point}, {blue point}} as XYZ) + //all use standard illuminant D65 and gamma 2.2 unless otherwise noted + static const T sRGB [3][3];//standard RGB (custom gamma) + static const T cieRGB [3][3];//CIE (1931) RGB (standard illuminant E) + static const T appleRGB[3][3];//Apple RGB (1.8 gamma) + static const T palRGB [3][3];//PAL / SECAM RGB + static const T ntscRGB [3][3];//NTSC / SMPTE C RGB + static const T adobeRGB[3][3];//Adobe RGB (1998) + + //sRGB custom gamma constants + static const T sA ; //deviation of gamma correction coefficient from 1 + static const T sGamma;//gamma exponent + static const T sPhi; //scale factor for linear gamma region + static const T sK0; //cutoff for linear gamma correction in inverse direction + + //sRGB <--> XYZ conversion matrices + static const std::array sRGBmat ;//matrix to convert from XYZ --> sRGB + static const std::array sRGBmatInv;//matrix to convert from sRGB --> XYZ + }; + + //standard illuminants as xyz (normalized XYZ) + template const T Standards::A_2 [3] = {T(0.44757), T(0.40745), T(0.14498)}; + template const T Standards::A_10 [3] = {T(0.45117), T(0.40594), T(0.14289)}; + template const T Standards::B_2 [3] = {T(0.34842), T(0.35161), T(0.29997)}; + template const T Standards::B_10 [3] = {T(0.34980), T(0.35270), T(0.29750)}; + template const T Standards::C_2 [3] = {T(0.31006), T(0.31616), T(0.37378)}; + template const T Standards::C_10 [3] = {T(0.31039), T(0.31905), T(0.37056)}; + template const T Standards::D50_2 [3] = {T(0.34567), T(0.35850), T(0.29583)}; + template const T Standards::D50_10[3] = {T(0.34773), T(0.35952), T(0.29275)}; + template const T Standards::D55_2 [3] = {T(0.33242), T(0.34743), T(0.32015)}; + template const T Standards::D55_10[3] = {T(0.33411), T(0.34877), T(0.31712)}; + template const T Standards::D65_2 [3] = {T(0.31271), T(0.32902), T(0.35827)}; + template const T Standards::D65_10[3] = {T(0.31382), T(0.33100), T(0.35518)}; + template const T Standards::D75_2 [3] = {T(0.29902), T(0.31485), T(0.38613)}; + template const T Standards::D75_10[3] = {T(0.29968), T(0.31740), T(0.38292)}; + template const T Standards::E [3] = {T(1)/T(3) , T(1)/T(3) , T(1)/T(3) }; + + //RGB chromaticities as xyz (normalized XYZ) + template const T Standards::sRGB [3][3] = {T(0.6400), T(0.3300), T(0.0300), + T(0.3000), T(0.6000), T(0.1000), + T(0.1500), T(0.0600), T(0.7900)}; + template const T Standards::cieRGB [3][3] = {T(0.7347), T(0.2653), T(0.0000), + T(0.2738), T(0.7174), T(0.0088), + T(0.1666), T(0.0089), T(0.8245)}; + template const T Standards::appleRGB[3][3] = {T(0.6250), T(0.3400), T(0.0350), + T(0.2800), T(0.5950), T(0.1250), + T(0.1550), T(0.0700), T(0.7750)}; + template const T Standards::adobeRGB[3][3] = {T(0.6400), T(0.3300), T(0.0300), + T(0.2100), T(0.7100), T(0.0800), + T(0.1500), T(0.0600), T(0.7900)}; + template const T Standards::palRGB [3][3] = {T(0.6400), T(0.3300), T(0.0300), + T(0.2900), T(0.6000), T(0.1100), + T(0.1500), T(0.0600), T(0.7900)}; + template const T Standards::ntscRGB [3][3] = {T(0.6300), T(0.3400), T(0.0300), + T(0.3100), T(0.5950), T(0.0950), + T(0.1550), T(0.0700), T(0.7750)}; + + //sRGB gamma correction constants + template const T Standards::sA = T(0.055 ); + template const T Standards::sGamma = T(2.4 ); + template const T Standards::sPhi = T(12.92 ); + template const T Standards::sK0 = T(0.04045); + + //sRGB conversion matrices + template const std::array Standards::sRGBmat = detail::rgbMat(Standards::sRGB, Standards::D65_2); + template const std::array Standards::sRGBmatInv = detail::inv3x3(Standards::sRGBmat); + } + + //////////////////////////////////////////// + // implementation of direct conversions // + //////////////////////////////////////////// + + //@brief : convert from XYZ to sRGB + //@param xyz: XYZ (X, Y, Z) values to convert + //@param rgb: location to write sRGB (red, green, blue) values + //@return : true/false if xyz falls outside/inside the sRGB color gamut + template bool xyz2rgb(T const * const xyz, T * const rgb) { + using namespace detail; + static const T gammaInv = T(1) / Standards::sGamma; + static const T k0Inv = Standards::sK0 / Standards::sPhi; + static const T a1 = T(1) + Standards::sA; + T work[3]; + for(size_t i = 0; i < 3; i++) work[i] = std::inner_product(xyz, xyz+3, Standards::sRGBmat.begin() + 3*i, T(0));//XYZ -> linear rgb + for(size_t i = 0; i < 3; i++) rgb[i] = work[i] <= k0Inv ? work[i] * Standards::sPhi : a1 * std::pow(work[i], gammaInv) - Standards::sA;//gamma correction + + //check if this value is outside the sRGB color gamut + bool clamped = false; + for(size_t i = 0; i < 3; i++) { + if(std::signbit(rgb[i])) { + rgb[i] = T(0); + clamped = true; + } else if(rgb[i] > T(1)) { + rgb[i] = T(1); + clamped = true; + } + } + return clamped; + } + + //@brief : convert from sRGB to XYZ + //@param rgb: sRGB (red, green, blue) values to convert + //@param xyz: location to write XYZ (X, Y, Z) values + template void rgb2xyz(T const * const rgb, T * const xyz) { + using namespace detail; + static const T a1 = T(1) + Standards::sA; + T work[3]; + for(size_t i = 0; i < 3; i++) xyz[i] = rgb[i] <= Standards::sK0 ? rgb[i] / Standards::sPhi : std::pow((rgb[i]+Standards::sA) / a1, Standards::sGamma);//gamma correction + for(size_t i = 0; i < 3; i++) work[i] = std::inner_product(xyz, xyz+3, Standards::sRGBmatInv.begin() + 3*i, T(0));//XYZ -> linear rgb + std::copy(work, work+3, xyz); + } + + //@brief : convert from XYZ to Lab + //@param xyz: XYZ (X, Y, Z) values to convert + //@param lab: location to write Lab (L*, a*, b*) values + //@param ill: Lab illuminant as XYZ (or NULL to use illuminant D65 for a 2 degree observer) + template void xyz2lab(T const * const xyz, T * const lab, T const * ill) { + //initialize constants once + static const T delta = T(6) / 29; + static const T d2 = delta * delta * 3; + static const T d3 = delta * delta * delta; + static const T k = T(4) / 29; + + //compute f(i/i_N) + T fXYZ[3]; + T const * const illum = (NULL != ill) ? ill : detail::Standards::D65_2; + for(size_t i = 0; i < 3; i++) { + const T t = xyz[i] / (illum[i] / illum[1]);//normalize with respect to white point + fXYZ[i] = t > d3 ? std::cbrt(t) : t / d2 + k;//apply nonlinear scaling + } + + //change basis and scale + lab[0] = fXYZ[1] * 116 - 16;//L* + lab[1] = (fXYZ[0] - fXYZ[1]) * 500;//a* + lab[2] = (fXYZ[1] - fXYZ[2]) * 200;//b* + } + + //@brief : convert from Lab to xyz + //@param lab: Lab (L*, a*, b*) values to convert + //@param xyz: location to write XYZ (X, Y, Z) values + //@param ill: Lab illuminant as XYZ (or NULL to use illuminant D65 for a 2 degree observer) + template void lab2xyz(T const * const lab, T * const xyz, T const * ill) { + //initialize constants once + static const T delta = T(6) / 29; + static const T d2 = delta * delta * 3; + static const T d3 = delta * delta * delta; + static const T k = T(4) / 29; + + //change basis and scale + const T Ln = (lab[0] + T(16)) / 116; + xyz[0] = Ln + lab[1] / 500; + xyz[1] = Ln; + xyz[2] = Ln - lab[2] / 200; + + //compute inverse of f(i/i_N) + T const * const illum = (NULL != ill) ? ill : detail::Standards::D65_2; + for(size_t i = 0; i < 3; i++) { + xyz[i] = xyz[i] > delta ? xyz[i] * xyz[i] * xyz[i] : d2 * (xyz[i] - k);//remove nonlinear scaling + xyz[i] *= illum[i] / illum[1];//remove white point normalization + } + } + + //@brief : convert from XYZ to Luv + //@param xyz: XYZ (X, Y, Z) values to convert + //@param luv: location to write Luv (L*, u*, v*) values + //@param ill: Lab illuminant as XYZ (or NULL to use illuminant D65 for a 2 degree observer) + template void xyz2luv(T const * const xyz, T * const luv, T const * ill) { + //initialize constants once + static const T d = T(216) / 24389;//(6/29)^3 + static const T d_8 = T(8) / d;//(29/3)^3 + + //compute normalized X, Y, and Z and denomentators and u/v + T const * const illum = (NULL != ill) ? ill : detail::Standards::D65_2; + const T denn = (illum[0] + illum[1] * 15 + illum[2] * 3) / illum[1]; + const T den = xyz [0] + xyz [1] * 15 + xyz [2] * 3; + const bool zero = T(0) == den; + const T u = (zero ? T(0) : xyz[0] / den) - (illum[0] / illum[1]) / denn; + const T v = (zero ? T(0) : xyz[1] / den) - T(1) / denn; + + //compute Luv + luv[0] = xyz[1] <= d ? xyz[1] * d_8 : std::cbrt(xyz[1]) * 116 - 16; + luv[1] = luv[0] * 52 * u; + luv[2] = luv[0] * 117 * v; + } + + //@brief : convert from Luv to xyz + //@param luv: Luv (L*, u*, v*) values to convert + //@param xyz: location to write XYZ (X, Y, Z) values + //@param ill: Lab illuminant as XYZ (or NULL to use illuminant D65 for a 2 degree observer) + template void luv2xyz(T const * const luv, T * const xyz, T const * ill) { + //handle L* = 0 + if(luv[0] == T(0)) { + std::fill(xyz, xyz+3, T(0)); + return; + } + + //compute u' and v' + T const * const illum = (NULL != ill) ? ill : detail::Standards::D65_2; + const T denn = (illum[0] + illum[1] * 15 + illum[2] * 3) / illum[1]; + const T up = (luv[1] / 13 + luv[0] * (illum[0] / illum[1]) * 4 / denn) * 3; + const T vp = (luv[2] / 13 + luv[0] * 9 / denn) * 4; + const T Lp = (luv[0] + 16) / 116; + + //compute X, Y, and Z + static const T d = T(27) / 24389;//(3/29)^3 + xyz[1] = luv[0] <= 8 ? luv[0] * d : Lp * Lp * Lp; + xyz[2] = xyz[1] * (T(12) * luv[0] - up - vp * 5) / vp; + xyz[0] = xyz[1] * (up * 3) / vp; + } + + //@brief : convert from hsv to rgb + //@param hsv: hsv (hue, saturation, value) values to convert + //@param rgb: location to write rgb (red, green, blue) values + template void hsv2rgb(T const * const hsv, T * const rgb) { + const T c = hsv[1] * hsv[2];//chroma + detail::hcm2rgb(hsv[0], c, hsv[2] - c , rgb); + } + + //@brief : convert from hsl to rgb + //@param hsl: hsl (hue, saturation, lightness) values to convert + //@param rgb: location to write rgb (red, green, blue) values + template void hsl2rgb(T const * const hsl, T * const rgb) { + const T c = (T(1) - std::fabs(hsl[2] * 2 - 1)) * hsl[1];//chroma + detail::hcm2rgb(hsl[0], c, hsl[2] - c/2, rgb); + } + + //@brief : convert from hsl to hsv + //@param hsl: hsl (hue, saturation, lightness) values to convert + //@param hsv: location to write hsv (hue, saturation, value) values + template void hsl2hsv(T const * const hsl, T * const hsv) { + const T s = hsl[1] * (hsl[2] < T(0.5) ? hsl[2] : T(1) - hsl[2]); + hsv[0] = hsl[0]; + hsv[2] = s + hsl[2]; + hsv[1] = T(0) == s ? T(0) : T(2) * s / hsv[2]; + } + + //@brief : convert from hsv to hsl + //@param hsv: hsv (hue, saturation, lightness) values to convert + //@param hsl: location to write hsl (hue, saturation, value) values + template void hsv2hsl(T const * const hsv, T * const hsl) { + const T sv = hsv[1] * hsv[2]; + const T x = 2 * hsv[2] - sv; + hsl[0] = hsv[0]; + hsl[2] = hsv[2] - sv / 2; + hsl[1] = T(0) == sv ? T(0) : sv / (x < T(1) ? x : T(2) - x); + } + + //@brief : convert rgb to hsv + //@param rgb: sRGB (red, green, blue) values to convert + //@param hsv: location to write hsv (hue, saturation, value) values + template void rgb2hsv(T const * const rgb, T * const hsv) { + auto minMax = std::minmax_element(rgb, rgb+3); + const T vMax = *(minMax.second); + if(T(0) == vMax) {//black + std::fill(hsv, hsv + 3, T(0)); + } else { + const T vMin = *(minMax.first ); + const T delta = vMax - vMin; + if(T(0) == delta) {//gray + hsv[0] = hsv[1] = T(0); + } else { + const size_t indMax = std::distance(rgb, minMax.second); + hsv[0] = T(indMax) / 3 + (rgb[(indMax+1)%3] - rgb[(indMax+2)%3]) / (delta * 6); + if(std::signbit(hsv[0])) hsv[0] += T(1); + if(hsv[0] == T(1)) hsv[0] = T(0);// if instead of else if to handle -0 + hsv[1] = delta / vMax; + } + hsv[2] = vMax; + } + } + + //@brief : convert rgb to hsl + //@param rgb: sRGB (red, green, blue) values to convert + //@param hsl: location to write hsl (hue, saturation, lightness) values + template void rgb2hsl(T const * const rgb, T * const hsl) { + auto minMax = std::minmax_element(rgb, rgb+3); + const T vMax = *(minMax.second); + if(T(0) == vMax) {//black + std::fill(hsl, hsl + 3, T(0)); + } else { + const T vMin = *(minMax.first ); + const T delta = vMax - vMin; + const T sigma = vMax + vMin; + if(T(0) == delta) {//gray + hsl[0] = hsl[1] = T(0); + } else { + const size_t indMax = std::distance(rgb, minMax.second); + hsl[0] = T(indMax) / 3 + (rgb[(indMax+1)%3] - rgb[(indMax+2)%3]) / (delta * 6);//hue + if(std::signbit(hsl[0])) hsl[0] += T(1); + hsl[1] = delta / (sigma < T(1) ? sigma: T(2) - sigma);//saturation + } + hsl[2] = sigma / 2;//lightness + } + } + + //////////////////////////////////////////// + // implementation of indirect conversions // + //////////////////////////////////////////// + + //illuminant free cie spaces -> hsl/hsv (using cie spaces -> rgb) + template bool xyz2hsv(T const * const xyz, T * const hsv ) {const bool b = xyz2rgb(xyz, hsv); rgb2hsv(hsv, hsv); return b;}//xyz->rgb->hsv + template bool xyz2hsl(T const * const xyz, T * const hsl ) {const bool b = xyz2rgb(xyz, hsl); rgb2hsl(hsl, hsl); return b;}//xyz->rgb->hsl + + //illuminated cie spaces -> rgb/hsv/hsl (using cie spaces -> xyz -> rgb) + template bool luv2rgb(T const * const luv, T * const rgb, T const * ill) {luv2xyz(luv, rgb, ill); return xyz2rgb(rgb, rgb);}//luv->xyz->rgb + template bool luv2hsv(T const * const luv, T * const hsv, T const * ill) {luv2xyz(luv, hsv, ill); return xyz2hsv(hsv, hsv);}//luv->xyz->rgb->hsv + template bool luv2hsl(T const * const luv, T * const hsl, T const * ill) {luv2xyz(luv, hsl, ill); return xyz2hsl(hsl, hsl);}//luv->xyz->rgb->hsl + template bool lab2rgb(T const * const lab, T * const rgb, T const * ill) {lab2xyz(lab, rgb, ill); return xyz2rgb(rgb, rgb);}//lab->xyz->rgb + template bool lab2hsv(T const * const lab, T * const hsv, T const * ill) {lab2xyz(lab, hsv, ill); return xyz2hsv(hsv, hsv);}//lab->xyz->rgb->hsv + template bool lab2hsl(T const * const lab, T * const hsl, T const * ill) {lab2xyz(lab, hsl, ill); return xyz2hsl(hsl, hsl);}//lab->xyz->rgb->hsl + + //within cie spaces (through xyz) + template void luv2lab(T const * const luv, T * const lab, T const * ill) {luv2xyz(luv, lab, ill); xyz2lab(lab, lab, ill);}//luv->xyz->lab + template void lab2luv(T const * const lab, T * const luv, T const * ill) {lab2xyz(lab, luv, ill); xyz2luv(luv, luv, ill);}//lab->xyz->luv + + //rgb/hsv/hsl to cie spaces (all go through rgb -> xyz) + template void rgb2luv(T const * const rgb, T * const luv, T const * ill) { rgb2xyz(rgb, luv); xyz2luv(luv, luv, ill);}// rgb->xyz->luv + template void rgb2lab(T const * const rgb, T * const lab, T const * ill) { rgb2xyz(rgb, lab); xyz2lab(lab, lab, ill);}// rgb->xyz->lab + template void hsv2xyz(T const * const hsv, T * const xyz ) {hsv2rgb(hsv, xyz); rgb2xyz(xyz, xyz); }//hsv->rgb->xyz + template void hsv2luv(T const * const hsv, T * const luv, T const * ill) {hsv2rgb(hsv, luv); rgb2xyz(luv, luv); xyz2luv(luv, luv, ill);}//hsv->rgb->xyz->luv + template void hsv2lab(T const * const hsv, T * const lab, T const * ill) {hsv2rgb(hsv, lab); rgb2xyz(lab, lab); xyz2lab(lab, lab, ill);}//hsv->rgb->xyz->lab + template void hsl2xyz(T const * const hsl, T * const xyz ) {hsl2rgb(hsl, xyz); rgb2xyz(xyz, xyz); }//hsl->rgb->xyz + template void hsl2luv(T const * const hsl, T * const luv, T const * ill) {hsl2rgb(hsl, luv); rgb2xyz(luv, luv); xyz2luv(luv, luv, ill);}//hsl->rgb->xyz->luv + template void hsl2lab(T const * const hsl, T * const lab, T const * ill) {hsl2rgb(hsl, lab); rgb2xyz(lab, lab); xyz2lab(lab, lab, ill);}//hsl->rgb->xyz->lab +} + +#endif//_COLORSPACE_H_ diff --git a/include/util/fft.hpp b/include/util/fft.hpp new file mode 100644 index 0000000..e142d7f --- /dev/null +++ b/include/util/fft.hpp @@ -0,0 +1,652 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _FFTW_WRAP_ +#define _FFTW_WRAP_ + +#include +#include +#include + +#include "fftw3.h" + +//preprocessor macros to use fftw float, double, and/or long +//these can be hard coded here if needed but should be define by cmake +// #define EM_USE_F +// #define EM_USE_D +// #define EM_USE_L + +namespace fft { + namespace flag { + enum class Plan { + Estimate = FFTW_ESTIMATE ,//only estimate fastest algorithm (fastest plan creation but slowest execution) + Measure = FFTW_MEASURE ,//measure a few paths to determine fastest algorithm [~seconds to create] + Patient = FFTW_PATIENT ,//measure many paths to determine fastest algorithm [several times slower than measure] + Exhaustive = FFTW_EXHAUSTIVE ,//measure all paths to determine fastest algorithm (slowest plan creation but fastest execution) [significantly longer] + // WisdomOnly = FFTW_WISDOM_ONLY,//only create a plan if wisdom is available + }; + + enum class Input { + Destroy = FFTW_DESTROY_INPUT ,//out of place transforms can destroy input + Preserve = FFTW_PRESERVE_INPUT,//out of place transform must preserve input + // Unaligned = FFTW_UNALIGNED ,//prevent special alignment restrictions (disables SIMD acceleration) + }; + } + + namespace detail { + //templated helper to hold an fftw plan + template struct Plan {static_assert(std::is_same::value || std::is_same::value || std::is_same::value, "Real must be float, double");}; + + //helper to manage fftw wisdom + template + struct Wisdom { + //@brief: import existing wisdom from a file + void read(); + + //@brief: export accumulated wisdom to a file + void write(); + + Wisdom() {read();}//import existing wisdom on creation + ~Wisdom() {write();}//export existing wisdom on cleanup + }; + + //single global instance for types of interest to automatically load/save wisdom + #ifdef EM_USE_F + Wisdom< float > fWisdom; + #endif + #ifdef EM_USE_D + Wisdom< double> dWisdom; + #endif + #ifdef EM_USE_L + Wisdom lWisdom; + #endif + } + + //@brief: find the closest FFT size that is fast + //@param x: minimum FFT + //@return : smallest fast FFT size that is greater or equal to x + uint32_t fastSize(const uint32_t x); + + //helper to allow stl containers with fftw_malloc + template struct allocator { + typedef T value_type;//typedef for allocator_traits + allocator( ) noexcept { }//constructor for allocator_traits + template allocator(const allocator&) noexcept { }//template constructor for allocator_traits + T* allocate ( size_t n ) {return (T*) fftw_malloc(n * sizeof(T));}//allocate with fftw_malloc instead of new + void deallocate (T* p, size_t n ) { fftw_free ((void*) p );}//free with fftw_free instead of delete + }; + template using vector = std::vector >;//use fft::vector instead of std::vector to use fftw allocation + + //templated helper to wrap a pair of forward/reverse transformations for real data + template struct RealFFT { + detail::Plan pFor, pInv; + + //@brief : construct the forward and reverse FFT plans + //@param n : length of FFT + //@param pFlag : fft planning flag + //@param destroy: can the input be destroyed during execution + RealFFT(const size_t n, const flag::Plan pFlag, const bool destroy = false); + + //@brief : compute an FFT from input data + //@param signal : data to compute FFT of + //@param spectra: location to write FFT of signal + void forward(Real* signal, std::complex* spectra) const; + + //@brief : compute an inverse FFT from input FFT + //@param spectra: spectra to reconstruct signal from + //@param signal : location to write reconstructed data + void inverse(std::complex* spectra, Real* signal) const; + }; + + //templated helper to wrap an inverse transformation of 3D real data + template struct RealFFT3D { + detail::Plan pInv; + + //@brief : construct the forward and reverse FFT plans + //@param n : side length of 3D FFT + //@param pFlag: fft planning flag + RealFFT3D(const size_t n, const flag::Plan pFlag);//mutli dimensional c2r transforms cannot preserve input + + //@brief : compute an inverse FFT from input FFT + //@param spectra: spectra to reconstruct signal from + //@param signal : location to write reconstructed data + void inverse(std::complex* spectra, Real* signal) const; + }; + + //templated helper to wrap a inverse transformation of 3D real data (seperated into components for each dimension) + template struct SepRealFFT3D { + const int vN, vH;//full and halfcomplex sizes + detail::Plan pX, pY, pZ;//planes for transforms along x, y, and z axis + + //@brief : construct the FFT plans + //@param n : side length of 3D FFT + //@param pFlag: fft planning flag + SepRealFFT3D(const size_t n, const flag::Plan pFlag);//mutli dimensional c2r transforms cannot preserve input + + //@brief : compute an inverse FFT from input FFT + //@param spectra: spectra to reconstruct signal from + //@param signal : location to write reconstructed data + //@param dx : frequency of YZ planes with non zero elements + void inverse(std::complex* spectra, Real* signal, const size_t dx = 1) const; + }; + + template struct DCT2D { + detail::Plan plan; + + //@brief : construct DCT plan + //@param width : 2d array width (fast index) + //@param height : 2d array height (slow index) + //@param pFlag : fft planning flag + //@param reverse: true/false for reverse/forward cosine transform + //@param destroy: can the input be destroyed during execution + DCT2D(const size_t width, const size_t height, const flag::Plan pFlag, const bool reverse, const bool destroy = false); + + //@brief : execute the planned cosine transform on a new array + //@param input : input signal/spectra + //@param output: location to write output signal/spectra + void execute(Real* input, Real* output) const; + }; + + template struct DCT { + detail::Plan plan; + + //@brief : construct DCT plan + //@param length : 1d array size + //@param pFlag : fft planning flag + //@param reverse: true/false for reverse/forward cosine transform + //@param destroy: can the input be destroyed during execution + DCT(const size_t length, const flag::Plan pFlag, const bool reverse, const bool destroy = false); + + //@brief : execute the planned cosine transform on a new array + //@param input : input signal/spectra + //@param output: location to write output signal/spectra + void execute(Real* input, Real* output) const; + }; +} + +//////////////////////////////////////////////////////////////////////// +// Implementations // +//////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include + +#include "sysnames.hpp" + +namespace fft { + namespace detail { + //float + #ifdef EM_USE_F + template <> struct Plan { + fftwf_plan p ;//fftw plane + std::mutex mut;//mutex for cleanup when plan is shared across threads + + Plan() : p(NULL) {}//make sure we don't try to clean up random memory on destruction + + //destructor (needs to be thread safe) + ~Plan() { + std::unique_lock lock(mut);//make sure another thread isn't currently calling the destructor + if(NULL != p) fftwf_destroy_plan(p);//don't destroy plan if it was already cleaned up of never allocoated + } + }; + #endif + + //double + #ifdef EM_USE_D + template <> struct Plan { + fftw_plan p ;//fftw plane + std::mutex mut;//mutex for cleanup when plan is shared across threads + + Plan() : p(NULL) {}//make sure we don't try to clean up random memory on destruction + + //destructor (needs to be thread safe) + ~Plan() { + std::unique_lock lock(mut);//make sure another thread isn't currently calling the destructor + if(NULL != p) fftw_destroy_plan(p);}//don't destroy plan if it was already cleaned up of never allocoated + + }; + #endif + + //long double + #ifdef EM_USE_L + template <> struct Plan { + fftwl_plan p ;//fftw plane + std::mutex mut;//mutex for cleanup when plan is shared across threads + + Plan() : p(NULL) {}//make sure we don't try to clean up random memory on destruction + + //destructor (needs to be thread safe) + ~Plan() { + std::unique_lock lock(mut);//make sure another thread isn't currently calling the destructor + if(NULL != p) fftwl_destroy_plan(p);//don't destroy plan if it was already cleaned up of never allocoated + } + }; + #endif + + //@brief: helper function to check if a file exists + //@param name: name of file to look for + //@return: true/false if file does/doesn't exist + bool fileExists(std::string name) { + std::ifstream is(name); + return is.good(); + } + + //@brief: import existing wisdom from a file + #ifdef EM_USE_F + template <> void Wisdom< float >::read() { + const std::string fileName = getSharedDataDir() + "fftwf.wisdom"; + if(fileExists(fileName))//only try if the file exists + if(!fftwf_import_wisdom_from_filename(fileName.c_str())) + throw std::runtime_error("failed to read wisdom from " + fileName); + } + #endif + #ifdef EM_USE_D + template <> void Wisdom< double>::read() { + const std::string fileName = getSharedDataDir() + "fftw.wisdom"; + if(fileExists(fileName))//only try if the file exists + if(!fftw_import_wisdom_from_filename (fileName.c_str())) + throw std::runtime_error("failed to read wisdom from " + fileName); + } + #endif + #ifdef EM_USE_L + template <> void Wisdom::read() { + const std::string fileName = getSharedDataDir() + "fftwl.wisdom"; + if(fileExists(fileName))//only try if the file exists + if(!fftwl_import_wisdom_from_filename(fileName.c_str())) + throw std::runtime_error("failed to read wisdom from " + fileName); + } + #endif + + //@brief: export accumulated wisdom to a file + #ifdef EM_USE_F + template <> void Wisdom< float >::write() { + const std::string fileName = getSharedDataDir() + "fftwf.wisdom"; + if(!fftwf_export_wisdom_to_filename(fileName.c_str())) + std::cerr << "failed to write wisdom to " << fileName << '\n';//destructor can't throw, at least print a warning + } + #endif + #ifdef EM_USE_D + template <> void Wisdom< double>::write() { + const std::string fileName = getSharedDataDir() + "fftw.wisdom"; + if(!fftw_export_wisdom_to_filename (fileName.c_str())) + std::cerr << "failed to write wisdom to " << fileName << '\n';//destructor can't throw, at least print a warning + } + #endif + #ifdef EM_USE_L + template <> void Wisdom::write() { + const std::string fileName = getSharedDataDir() + "fftwl.wisdom"; + if(!fftwl_export_wisdom_to_filename(fileName.c_str())) + std::cerr << "failed to write wisdom to " << fileName << '\n';//destructor can't throw, at least print a warning + } + #endif + } + + //@brief: find the closest FFT size that is fast + //@param x: minimum FFT + //@return : smallest fast FFT size that is greater or equal to x + uint32_t fastSize(const uint32_t x) { + //handle special/easy cases + if(x <= 16) return std::max(1, x);//fftw explicitly implements all ffts from 1 -> 16 + + //start by computing the next power of 2 up from x + //https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + uint32_t v2x = x;//start with first power of 2 >= x + v2x--; + v2x |= v2x >> 1; + v2x |= v2x >> 2; + v2x |= v2x >> 4; + v2x |= v2x >> 8; + v2x |= v2x >> 16; + v2x++;//v2x now holds first poewr of 2 >= x + + //now compute the log_2 of v2x + //https://graphics.stanford.edu/~seander/bithacks.html#IntegerLog + static const uint32_t b[] = {0xAAAAAAAA, 0xCCCCCCCC, 0xF0F0F0F0, 0xFF00FF00, 0xFFFF0000}; + uint32_t ln2x = (v2x & b[0]) != 0; + for(int i = 4; i > 0; i--) ln2x |= ((v2x & b[i]) != 0) << i; + + //next compute log_2(log_2(v2x)) [since we'll be squaring in the last step] + uint32_t maxIter = (ln2x & b[0]) != 0; + for(int i = 4; i > 0; i--) maxIter |= ((ln2x & b[i]) != 0) << i; + + //now compute all combinations of 2^i * 3^j * 5^k... for fast (small) primes + //small primes for fftw are: 2, 3, 5, 7, 11, and 13 + //i, j, k ... only need to be checked for i < r; + std::set s; + s.insert( 2); + s.insert( 3); + s.insert( 5); + s.insert( 7); + s.insert(11); + s.insert(13); + uint32_t vMin = v2x;//our initial guess for smallest fast size is the next power of 2 up + for(uint32_t iter = 0; iter < maxIter; iter++) {//loop over required iterations + std::set sNew;//set to hold new elements to be added + for(const uint32_t& i : s) {//loop over current elements once + for(const uint32_t& j : s) {//loop over current elements twice + const uint32_t v = i * j;//compute product of elements + if(v < vMin) {//is this element small enough to care about (smaller than our current best) + if(v < x) {//values less than x are prefactors + sNew.insert(v); + } else {//otherwise (>= x) we've found a new best size + vMin = v; + } + } + } + } + s.insert(sNew.cbegin(), sNew.cend());//add our new pre factors + } + return vMin; + } + + //@brief : construct the forward and reverse FFT plans + //@param n : length of FFT + //@param pFlag : fft planning flag + //@param destroy: can the input be destroyed during execution +#ifdef EM_USE_F + template <> RealFFT< float >::RealFFT(const size_t n, const flag::Plan pFlag, const bool destroy) { + std::vector< float > work1(n); + std::vector< std::complex< float > > work2(n); + pFor.p = fftwf_plan_dft_r2c_1d((int)n, work1.data(), (fftwf_complex*)work2.data(), (destroy ? FFTW_DESTROY_INPUT : FFTW_PRESERVE_INPUT) | (int)pFlag); + pInv.p = fftwf_plan_dft_c2r_1d((int)n, (fftwf_complex*)work2.data(), work1.data(), (destroy ? FFTW_DESTROY_INPUT : FFTW_PRESERVE_INPUT) | (int)pFlag); + } +#endif +#ifdef EM_USE_D + template <> RealFFT< double>::RealFFT(const size_t n, const flag::Plan pFlag, const bool destroy) { + std::vector< double> work1(n); + std::vector< std::complex< double> > work2(n); + pFor.p = fftw_plan_dft_r2c_1d ((int)n, work1.data(), (fftw_complex *)work2.data(), (destroy ? FFTW_DESTROY_INPUT : FFTW_PRESERVE_INPUT) | (int)pFlag); + pInv.p = fftw_plan_dft_c2r_1d ((int)n, (fftw_complex *)work2.data(), work1.data(), (destroy ? FFTW_DESTROY_INPUT : FFTW_PRESERVE_INPUT) | (int)pFlag); + } +#endif +#ifdef EM_USE_L + template <> RealFFT::RealFFT(const size_t n, const flag::Plan pFlag, const bool destroy) { + std::vector work1(n); + std::vector< std::complex > work2(n); + pFor.p = fftwl_plan_dft_r2c_1d((int)n, work1.data(), (fftwl_complex*)work2.data(), (destroy ? FFTW_DESTROY_INPUT : FFTW_PRESERVE_INPUT) | (int)pFlag); + pInv.p = fftwl_plan_dft_c2r_1d((int)n, (fftwl_complex*)work2.data(), work1.data(), (destroy ? FFTW_DESTROY_INPUT : FFTW_PRESERVE_INPUT) | (int)pFlag); + } +#endif + + //@brief : compute an FFT from input data + //@param signal : data to compute FFT of + //@param spectra: location to write FFT of signal +#ifdef EM_USE_F + template <> void RealFFT< float >::forward( float * signal, std::complex< float >* spectra) const {fftwf_execute_dft_r2c(pFor.p, signal, (fftwf_complex*)spectra);} +#endif +#ifdef EM_USE_D + template <> void RealFFT< double>::forward( double* signal, std::complex< double>* spectra) const {fftw_execute_dft_r2c (pFor.p, signal, (fftw_complex *)spectra);} +#endif +#ifdef EM_USE_L + template <> void RealFFT::forward(long double* signal, std::complex* spectra) const {fftwl_execute_dft_r2c(pFor.p, signal, (fftwl_complex*)spectra);} +#endif + + //@brief : compute an inverse FFT from input FFT + //@param spectra: spectra to reconstruct signal from + //@param signal : location to write reconstructed data +#ifdef EM_USE_F + template <> void RealFFT< float >::inverse(std::complex< float >* spectra, float * signal) const {fftwf_execute_dft_c2r(pInv.p, (fftwf_complex*)spectra, signal);} +#endif +#ifdef EM_USE_D + template <> void RealFFT< double>::inverse(std::complex< double>* spectra, double* signal) const {fftw_execute_dft_c2r (pInv.p, (fftw_complex *)spectra, signal);} +#endif +#ifdef EM_USE_L + template <> void RealFFT::inverse(std::complex* spectra, long double* signal) const {fftwl_execute_dft_c2r(pInv.p, (fftwl_complex*)spectra, signal);} +#endif + + //@brief : construct the forward and reverse FFT plans + //@param n : side length of 3D FFT + //@param pFlag: fft planning flag +#ifdef EM_USE_F + template <> RealFFT3D< float >::RealFFT3D(const size_t n, const flag::Plan pFlag) { + std::vector< float > work1(n*n*n); + std::vector< std::complex< float > > work2(n*n*n); + pInv.p = fftwf_plan_dft_c2r_3d((int)n, (int)n, (int)n, (fftwf_complex*)work2.data(), work1.data(), (int)pFlag); + } +#endif +#ifdef EM_USE_D + template <> RealFFT3D< double>::RealFFT3D(const size_t n, const flag::Plan pFlag) { + std::vector< double> work1(n*n*n); + std::vector< std::complex< double> > work2(n*n*n); + pInv.p = fftw_plan_dft_c2r_3d ((int)n, (int)n, (int)n, (fftw_complex *)work2.data(), work1.data(), (int)pFlag); + } +#endif +#ifdef EM_USE_L + template <> RealFFT3D::RealFFT3D(const size_t n, const flag::Plan pFlag) { + std::vector work1(n*n*n); + std::vector< std::complex > work2(n*n*n); + pInv.p = fftwl_plan_dft_c2r_3d((int)n, (int)n, (int)n, (fftwl_complex*)work2.data(), work1.data(), (int)pFlag); + } +#endif + + //@brief : compute an inverse FFT from input FFT + //@param spectra: spectra to reconstruct signal from + //@param signal : location to write reconstructed data +#ifdef EM_USE_F + template <> void RealFFT3D< float >::inverse(std::complex< float >* spectra, float * signal) const {fftwf_execute_dft_c2r(pInv.p, (fftwf_complex*)spectra, signal);} +#endif +#ifdef EM_USE_D + template <> void RealFFT3D< double>::inverse(std::complex< double>* spectra, double* signal) const {fftw_execute_dft_c2r (pInv.p, (fftw_complex *)spectra, signal);} +#endif +#ifdef EM_USE_L + template <> void RealFFT3D::inverse(std::complex* spectra, long double* signal) const {fftwl_execute_dft_c2r(pInv.p, (fftwl_complex*)spectra, signal);} +#endif + + //@brief : construct the FFT plans + //@param n : side length of 3D FFT + //@param pFlag: fft planning flag +#ifdef EM_USE_F + template <> SepRealFFT3D< float >::SepRealFFT3D(const size_t n, const flag::Plan pFlag) : vN(int(n)), vH(int(n)/2 + 1) { + std::vector< float > work1(vN*vN*vN); + std::vector< std::complex< float > > work2(vN*vN*vH); + int rank = 1 ;//individual transforms are all 1D + int nn[1] = {vN} ;//individual transforms are all of length n + int howmany = vN ;//how many transformations will be performed (one down each z for each y at a single x) + fftwf_complex* in = (fftwf_complex*) work2.data() ;//input data + int* inembed = NULL ;//dimensions of super array that input is row major subarray of (null for not a subarray) + int istride = vN * vH ;//stride between sequential elements (z spacing) + int idist = vH ;//kth fft input at in + k * idist (y spacing) + fftwf_complex* out = (fftwf_complex*) work1.data() ;//output data + int* oenembed = NULL ;//dimensions of super array that output is row major subarray of (null for not a subarray) + int ostride = 1 ;//output stride + int odist = vN ;//kth fft outputs to out + k * odist + int sign = FFTW_BACKWARD ;//inverse transform + unsigned flags = FFTW_DESTROY_INPUT | (unsigned)pFlag;//planning flags + pZ.p = fftwf_plan_many_dft (rank, nn, howmany, in , inembed, istride, idist, out , oenembed, ostride, odist , sign, flags);//1st: transform down z for all y at a single x (into output array) + pX.p = fftwf_plan_many_dft_c2r(rank, nn, howmany, in , inembed, 1 , idist, work1.data(), oenembed, ostride, odist , flags);//3rd: transform down x for all y at a single z (into output array) + pY.p = fftwf_plan_many_dft (rank, nn, vH , out, inembed, vN , 1 , in , oenembed, vH , vN * vH, sign, flags);//2nd: transform down y for all z at a single x (into original 3d input from output) + } +#endif +#ifdef EM_USE_D + template <> SepRealFFT3D< double>::SepRealFFT3D(const size_t n, const flag::Plan pFlag) : vN(int(n)), vH(int(n)/2 + 1) { + std::vector< double > work1(vN*vN*vN); + std::vector< std::complex< double> > work2(vN*vN*vH); + int rank = 1 ;//individual transforms are all 1D + int nn[1] = {vN} ;//individual transforms are all of length n + int howmany = vN ;//how many transformations will be performed (one down each z for each y at a single x) + fftw_complex * in = (fftw_complex *) work2.data() ;//input data + int* inembed = NULL ;//dimensions of super array that input is row major subarray of (null for not a subarray) + int istride = vN * vH ;//stride between sequential elements (z spacing) + int idist = vH ;//kth fft input at in + k * idist (y spacing) + fftw_complex * out = (fftw_complex *) work1.data() ;//output data + int* oenembed = NULL ;//dimensions of super array that output is row major subarray of (null for not a subarray) + int ostride = 1 ;//output stride + int odist = vN ;//kth fft outputs to out + k * odist + int sign = FFTW_BACKWARD ;//inverse transform + unsigned flags = FFTW_DESTROY_INPUT | (unsigned)pFlag;//planning flags + pZ.p = fftw_plan_many_dft (rank, nn, howmany, in , inembed, istride, idist, out , oenembed, ostride, odist , sign, flags);//1st: transform down z for all y at a single x (into output array) + pX.p = fftw_plan_many_dft_c2r (rank, nn, howmany, in , inembed, 1 , idist, work1.data(), oenembed, ostride, odist , flags);//3rd: transform down x for all y at a single z (into output array) + pY.p = fftw_plan_many_dft (rank, nn, vH , out, inembed, vN , 1 , in , oenembed, vH , vN * vH, sign, flags);//2nd: transform down y for all z at a single x (into original 3d input from output) + } +#endif +#ifdef EM_USE_L + template <> SepRealFFT3D::SepRealFFT3D(const size_t n, const flag::Plan pFlag) : vN(int(n)), vH(int(n)/2 + 1) { + std::vector< long double > work1(vN*vN*vN); + std::vector< std::complex > work2(vN*vN*vH); + int rank = 1 ;//individual transforms are all 1D + int nn[1] = {vN} ;//individual transforms are all of length n + int howmany = vN ;//how many transformations will be performed (one down each z for each y at a single x) + fftwl_complex* in = (fftwl_complex*) work2.data() ;//input data + int* inembed = NULL ;//dimensions of super array that input is row major subarray of (null for not a subarray) + int istride = vN * vH ;//stride between sequential elements (z spacing) + int idist = vH ;//kth fft input at in + k * idist (y spacing) + fftwl_complex* out = (fftwl_complex*) work1.data() ;//output data + int* oenembed = NULL ;//dimensions of super array that output is row major subarray of (null for not a subarray) + int ostride = 1 ;//output stride + int odist = vN ;//kth fft outputs to out + k * odist + int sign = FFTW_BACKWARD ;//inverse transform + unsigned flags = FFTW_DESTROY_INPUT | (unsigned)pFlag;//planning flags + pZ.p = fftwl_plan_many_dft (rank, nn, howmany, in , inembed, istride, idist, out , oenembed, ostride, odist , sign, flags);//1st: transform down z for all y at a single x (into output array) + pX.p = fftwl_plan_many_dft_c2r(rank, nn, howmany, in , inembed, 1 , idist, work1.data(), oenembed, ostride, odist , flags);//3rd: transform down x for all y at a single z (into output array) + pY.p = fftwl_plan_many_dft (rank, nn, vH , out, inembed, vN , 1 , in , oenembed, vH , vN * vH, sign, flags);//2nd: transform down y for all z at a single x (into original 3d input from output) + } +#endif + + //@brief : compute an inverse FFT from input FFT + //@param spectra: spectra to reconstruct signal from + //@param signal : location to write reconstructed data + //@param dx : frequency of YZ planes with non zero elements +#ifdef EM_USE_F + template <> void SepRealFFT3D< float >::inverse(std::complex< float >* spectra, float * signal, const size_t dx) const { + const int delta = (int)delta; + for(int i = 0; i < vH; i+= delta) {//loop over yz planes doing 2d transforms + fftwf_execute_dft(pZ.p, (fftwf_complex*)spectra + i, (fftwf_complex*)signal ); + fftwf_execute_dft(pY.p, (fftwf_complex*)signal , (fftwf_complex*)spectra + i); + } + for(int i = 0; i < vH; i++) fftwf_execute_dft_c2r(pX.p, (fftwf_complex*)spectra + vN * vH * i, signal + vN * vN * i);//loop up xy planes doing batches of 1d c2r transforms, does 1 extra plane for extraction of 3x3x3 neighborhood at upper glide + } +#endif +#ifdef EM_USE_D + template <> void SepRealFFT3D< double>::inverse(std::complex< double>* spectra, double* signal, const size_t dx) const { + const int delta = (int)dx; + for(int i = 0; i < vH; i+= delta) {//loop over yz planes doing 2d transforms + fftw_execute_dft (pZ.p, (fftw_complex *)spectra + i, (fftw_complex *)signal ); + fftw_execute_dft (pY.p, (fftw_complex *)signal , (fftw_complex *)spectra + i); + } + for(int i = 0; i < vH; i++) fftw_execute_dft_c2r (pX.p, (fftw_complex *)spectra + vN * vH * i, signal + vN * vN * i);//loop up xy planes doing batches of 1d c2r transforms, does 1 extra plane for extraction of 3x3x3 neighborhood at upper glide + } +#endif +#ifdef EM_USE_L + template <> void SepRealFFT3D::inverse(std::complex* spectra, long double* signal, const size_t dx) const { + const int delta = (int)dx; + for(int i = 0; i < vH; i+= delta) {//loop over yz planes doing 2d transforms + fftwf_execute_dft(pZ.p, (fftwl_complex*)spectra + i, (fftwl_complex*)signal ); + fftwf_execute_dft(pY.p, (fftwl_complex*)signal , (fftwl_complex*)spectra + i); + } + for(int i = 0; i < vH; i++) fftwl_execute_dft_c2r(pX.p, (fftwl_complex*)spectra + vN * vH * i, signal + vN * vN * i);//loop up xy planes doing batches of 1d c2r transforms, does 1 extra plane for extraction of 3x3x3 neighborhood at upper glide + } +#endif + + //@brief : construct DCT plan + //@param width : 2d array width (fast index) + //@param height : 2d array height (slow index) + //@param pFlag : fft planning flag + //@param reverse: true/false for reverse/forward cosine transform + //@param destroy: can the input be destroyed during execution +#ifdef EM_USE_F + template <> DCT2D< float >::DCT2D(const size_t width, const size_t height, const flag::Plan pFlag, const bool reverse, const bool destroy) { + std::vector< float > work1(width*height), work2(width*height); + plan.p = fftwf_plan_r2r_2d((int)height , (int)width , work1.data(), work2.data(), reverse ? FFTW_REDFT01 : FFTW_REDFT10, reverse ? FFTW_REDFT01 : FFTW_REDFT10, (destroy ? FFTW_DESTROY_INPUT : FFTW_PRESERVE_INPUT) | (int)pFlag); + } +#endif +#ifdef EM_USE_D + template <> DCT2D< double>::DCT2D(const size_t width, const size_t height, const flag::Plan pFlag, const bool reverse, const bool destroy) { + std::vector< double> work1(width*height), work2(width*height); + plan.p = fftw_plan_r2r_2d ((int)height , (int)width , work1.data(), work2.data(), reverse ? FFTW_REDFT01 : FFTW_REDFT10, reverse ? FFTW_REDFT01 : FFTW_REDFT10, (destroy ? FFTW_DESTROY_INPUT : FFTW_PRESERVE_INPUT) | (int)pFlag); + } +#endif +#ifdef EM_USE_L + template <> DCT2D::DCT2D(const size_t width, const size_t height, const flag::Plan pFlag, const bool reverse, const bool destroy) { + std::vector work1(width*height), work2(width*height); + plan.p = fftwl_plan_r2r_2d((int)height , (int)width , work1.data(), work2.data(), reverse ? FFTW_REDFT01 : FFTW_REDFT10, reverse ? FFTW_REDFT01 : FFTW_REDFT10, (destroy ? FFTW_DESTROY_INPUT : FFTW_PRESERVE_INPUT) | (int)pFlag); + } +#endif + + //@brief : execute the planned cosine transform on a new array + //@param input : input signal/spectra + //@param output: location to write output signal/spectra +#ifdef EM_USE_F + template <> void DCT2D< float >::execute( float * input, float * output) const {fftwf_execute_r2r(plan.p, input, output);} +#endif +#ifdef EM_USE_D + template <> void DCT2D< double>::execute( double* input, double* output) const {fftw_execute_r2r (plan.p, input, output);} +#endif +#ifdef EM_USE_L + template <> void DCT2D::execute(long double* input, long double* output) const {fftwl_execute_r2r(plan.p, input, output);} +#endif + + //@brief : construct DCT plan + //@param length : 1d array size + //@param pFlag : fft planning flag + //@param reverse: true/false for reverse/forward cosine transform + //@param destroy: can the input be destroyed during execution +#ifdef EM_USE_F + template <> DCT< float >::DCT(const size_t length, const flag::Plan pFlag, const bool reverse, const bool destroy) { + std::vector< float > work1(length), work2(length); + plan.p = fftwf_plan_r2r_1d((int)length, work1.data(), work2.data(), reverse ? FFTW_REDFT01 : FFTW_REDFT10, (destroy ? FFTW_DESTROY_INPUT : FFTW_PRESERVE_INPUT) | (int)pFlag); + } +#endif +#ifdef EM_USE_D + template <> DCT< double>::DCT(const size_t length, const flag::Plan pFlag, const bool reverse, const bool destroy) { + std::vector< double> work1(length), work2(length); + plan.p = fftw_plan_r2r_1d ((int)length, work1.data(), work2.data(), reverse ? FFTW_REDFT01 : FFTW_REDFT10, (destroy ? FFTW_DESTROY_INPUT : FFTW_PRESERVE_INPUT) | (int)pFlag); + } +#endif +#ifdef EM_USE_L + template <> DCT::DCT(const size_t length, const flag::Plan pFlag, const bool reverse, const bool destroy) { + std::vector work1(length), work2(length); + plan.p = fftwl_plan_r2r_1d((int)length, work1.data(), work2.data(), reverse ? FFTW_REDFT01 : FFTW_REDFT10, (destroy ? FFTW_DESTROY_INPUT : FFTW_PRESERVE_INPUT) | (int)pFlag); + } +#endif + + //@brief : execute the planned cosine transform on a new array + //@param input : input signal/spectra + //@param output: location to write output signal/spectra +#ifdef EM_USE_F + template <> void DCT< float >::execute( float * input, float * output) const {fftwf_execute_r2r(plan.p, input, output);} +#endif +#ifdef EM_USE_D + template <> void DCT< double>::execute( double* input, double* output) const {fftw_execute_r2r (plan.p, input, output);} +#endif +#ifdef EM_USE_L + template <> void DCT::execute(long double* input, long double* output) const {fftwl_execute_r2r(plan.p, input, output);} +#endif +} + +#endif//_FFTW_WRAP_ diff --git a/include/util/gaussian.hpp b/include/util/gaussian.hpp new file mode 100644 index 0000000..a7f0a80 --- /dev/null +++ b/include/util/gaussian.hpp @@ -0,0 +1,389 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _GAUSSIAN_H_ +#define _GAUSSIAN_H_ + +#include +#include + +namespace gaussian { + //@brief a y-shifted gaussian function + template + struct Model { + + //model parameters + Real a;//mean + Real b;//2 * sigma^2 + Real c;//amplitude + + //@brief : evauluate function f(x) = c * exp( -(x-a)^2 / b ) + //@prarm x: where to evaulate + //@return : f(x) + Real evaluate(const Real x) const; + + //@brief : estimate the least squares fit gaussian model for a set of points + //@param x : x coordinates of points (or NULL to assume that x[i] == i) + //@param y: y coordinates of points + //@param n: number of points + //@return : mean value of y + Real estimate(Real const * const x, Real const * const y, size_t n); + + //@brief : compute the least squares fit gaussian model for a set of points using current parameters as guess + //@param x : x coordinates of points (or NULL to assume that x[i] == i) + //@param y : y coordinates of points + //@param n : number of points + //@param init: true to estimate initial values, false to use current values to initialize + //@note : minimizes sum( ( y - f(x) ) ^2 ) + //@return : coefficient of determination (R^2) + Real fit(Real const * const x, Real const * const y, size_t n, bool init = true); + + }; + + //@brief: 2D Gaussian background subtraction + template + struct BckgSub2D { + size_t m_w;//image width in pixels + size_t m_h;//image height in pixels + std::shared_ptr< std::vector > msk;//image mask + std::vector rWrk, cWrk;//work spaces for row / column + gaussian::Model gx, gy;//x/y gaussian fits + Real c;//combined x/y gaussian c + + //@brief : construct a background subtracter with an arbitrary mask + //@param w: image width in pixels + //@param h: image width in pixels + //@param m: pixel mask [assumed to be w * h with 0 for bad pixels 1 for good pixels (omitted to include all pixels) + BckgSub2D(const size_t w, const size_t h, std::shared_ptr< std::vector > m) : m_w(w), m_h(h), msk(m), rWrk(h), cWrk(w) {} + BckgSub2D(const size_t w = 0, const size_t h = 0) : BckgSub2D(w, h, std::make_shared< std::vector >(w * h, 1)) {} + + //@brief : construct a circularly masked background subtracter + //@param w: image width + //@param h: image height + //@param r: circle radius + static BckgSub2D CircMask(const int w, const int h, const int r); + static BckgSub2D CircMask(const int w, const int h) {return CircMask(w, h, std::min(w, h) / 2);} + + //@brief : fit background from an image + //@param im : image to subtract background from + template + void fit(TPix * const im); + + //@brief : in place subtract the 2d background from an image + //@param im: image to subtract background from (in place) + void subtract(uint8_t * const im); + void subtract(uint16_t* const im); + void subtract(float * const im); + + //@brief : out of place subtract the 2d background from an image + //@param im: image to subtract background from + //@param bf: location to write background subtracted image + template + void subtract(TPix const * const im, Real * const bf); + }; +} + +#include +#include +#include +#include + +#include "linalg.hpp" + +namespace gaussian { + //@brief : evauluate function f(x) = c * exp( -(x-a)^2 / b ) + //@prarm x: where to evaulate + //@return : f(x) + template Real Model::evaluate(const Real x) const { + static_assert(std::is_floating_point::value, "gaussian model must be templated on floating point type"); + + const Real dx = (x - a) ; + return c * std::exp( -(dx * dx / b) ); + } + + //@brief : estimate the least squares fit gaussian model for a set of points + //@param x : x coordinates of points (or NULL to assume that x[i] == i) + //@param y: y coordinates of points + //@param n: number of points + //@return : mean value of y + template Real Model::estimate(Real const * const x, Real const * const y, size_t n) { + static_assert(std::is_floating_point::value, "gaussian model must be templated on floating point type"); + + //estimate mean and scaling from max + Real const * const yMax = std::max_element(y, y + n); + const size_t iMax = std::distance(y, yMax); + c = *yMax; + a = NULL == x ? Real(iMax) : x[iMax]; + + //estimate b with linear regression of ln( y[i] / c ) == -(x-a)^2 / b + Real xy = 0, y2 = 0, yBar = 0; + for(size_t i = 0; i < n; i++) { + const Real yc = y[i] / c; + yBar += y[i]; + if(yc > 0) {//don't compute log(-#) + const Real dx = a - (NULL == x ? Real(i) : x[i]); + const Real yi = std::log( yc ); + y2 += yi * yi; + xy -= yi * dx * dx; + } + } + b = xy / y2; + return yBar / n; + } + + //@brief : compute the least squares fit gaussian model for a set of points using current parameters as guess + //@param x : x coordinates of points (or NULL to assume that x[i] == i) + //@param y : y coordinates of points + //@param n : number of points + //@param init: true to estimate initial values, false to use current values to initialize + //@note : minimizes sum( ( y - f(x) ) ^2 ) + //@return : coefficient of determination (R^2) + template Real Model::fit(Real const * const x, Real const * const y, size_t n, const bool init) { + if(n < 3) throw std::runtime_error("not enough points for 3 degrees of freedom"); + Real yBar = 0; + if(init) { + yBar = estimate(x, y, n); + } else { + yBar = std::accumulate(y, y + n, Real(0)) / n; + } + + //compute the total sum of squares once + Real ssTot = 0; + for(size_t i = 0; i < n; i++) { + const Real dy = y[i] - yBar; + ssTot += dy * dy; + } + + //do gauss newton iteration + const Real thr = (Real)0.0001;//convergence criterion (0.0001 => 0.01% relative step) + const size_t maxIter = 50; + Real ssPrev = 0, metricPrev = std::numeric_limits::max(); + for(size_t iter = 0; iter < maxIter; iter++) { + //compute jacobian^T * jacobian and jacobian^T * residuals + Real ss = 0; + Real jTj[9] = {Real(0)}, jTr[3] = {Real(0)}, step[3]; + for(size_t i = 0; i < n; i++) { + //compute derivatives of function w.r.t. x + const Real dx = a - (NULL == x ? Real(i) : x[i]); + const Real dxb = dx / b; + const Real dfdc = std::exp( -dx * dxb );//df(x)/dc @ xi + const Real fx = c * dfdc;//f(xi) + const Real fxb = fx * dxb ; + const Real dfda = -fxb * Real(2);//df(x)/da @ xi + const Real dfdb = fxb * dxb;//df(x)/db @ xi + const Real ri = y[i] - fx;//residual + + //accumulate + jTj[0] += dfda * dfda; jTj[1] += dfda * dfdb; jTj[2] += dfda * dfdc; jTr[0] += ri * dfda; + jTj[4] += dfdb * dfdb; jTj[5] += dfdb * dfdc; jTr[1] += ri * dfdb; + jTj[8] += dfdc * dfdc; jTr[2] += ri * dfdc; + ss += ri * ri; + } + + //make symmetric and compute step + jTj[3] = jTj[1]; + jTj[6] = jTj[2]; jTj[7] = jTj[5]; + solve::cholesky(jTj, step, jTr, 3); + + //apply step + a += step[0]; + b += step[1]; + c += step[2]; + + //check for convergence + const Real metric = std::fabs((ssPrev - ss) / ss); + if(metric >= metricPrev && metric < thr) return Real(1) - ss / ssTot; + metricPrev = metric; + ssPrev = ss; + } + throw std::runtime_error("failed to converged within allowed iterations"); + } + + //@brief : construct a circularly masked background subtracter + //@param w: image width + //@param h: image height + //@param r: circle radius + template + BckgSub2D BckgSub2D::CircMask(const int w, const int h, const int r) { + const int w2 = w/2; + const int h2 = h/2; + const int rr = r*r; + std::shared_ptr< std::vector > mask = std::make_shared< std::vector >(w * h, 0); + for(int j = 0; j < h; j++) { + const int y = j - h2; + for(int i = 0; i < w; i++) { + const int x = i - w2; + if(x * x + y * y <= rr) mask->operator[](j * w + i) = 1; + } + } + return BckgSub2D(w, h, mask); + } + + //@brief : fit background from an image + //@param im : image to subtract background from + template + template + void BckgSub2D::fit(TPix * const im) { + //start by computing row / column max + std::fill(rWrk.begin(), rWrk.end(), im[0]); + std::fill(cWrk.begin(), cWrk.end(), im[0]); + for(size_t j = 0; j < m_h; j++) { + for(size_t i = 0; i < m_w; i++) { + if(1 == msk->operator[](j*m_w+i)) { + const Real v = im[j*m_w+i]; + if(v > rWrk[j]) rWrk[j] = v; + if(v > cWrk[i]) cWrk[i] = v; + } + } + } + + //fit a guassian to row and column max + try { + gx.fit(NULL, cWrk.data()+1, m_w-2); + } catch (...) { + gx.a = Real(cWrk.size()) / 2;//mean in middle + gx.b = std::numeric_limits::infinity();//infinite stddev (flat) + gx.c = std::accumulate(cWrk.cbegin(), cWrk.cend(), Real(0)) / cWrk.size();//amplitude from average + } + try { + gy.fit(NULL, rWrk.data()+1, m_h-2); + } catch (...) { + gy.a = Real(rWrk.size()) / 2;//mean in middle + gy.b = std::numeric_limits::infinity();//infinite stddev (flat) + gy.c = std::accumulate(rWrk.cbegin(), rWrk.cend(), Real(0)) / rWrk.size();//amplitude from average + } + + //now re-estimate scaling using input image + /* + Real tk2 = 0, tky = 0; + for(int j = 0; j < h; j++) { + const Real dy = gy.a - j; + const Real fy = std::exp( - (dy * dy ) / gy.b ); + for(int i = 0; i < w; i++) { + if(1 == circMask[j * w + i]) { + const Real dx = gx.a - i; + const Real fx = std::exp( - (dx * dx ) / gx.b ); + const Real tk = fx * fy; + tk2 += tk * tk ; + tky += tk * ptr[j*w+i]; + } + } + } + c = tky / tk2; + */ + c = std::max(gx.c, gy.c);//this works essentially just as well but is much faster + + //compute x exponential once per column + for(int i = 0; i < m_w; i++) { + const Real dx = gx.a - i; + cWrk[i] = std::exp( -(dx * dx / gx.b) ) * c; + } + for(int j = 0; j < m_h; j++) { + const Real dy = gy.a - j; + rWrk[j] = std::exp( -(dy * dy / gy.b) ); + } + } + + //@brief : subtract the 2d background from an image + //@param im : image to subtract background from + template + void BckgSub2D::subtract(float * const im) { + if(std::is_floating_point::value) { + for(int j = 0; j < m_h; j++) { + for(int i = 0; i < m_w; i++) { + const size_t idx = j*m_w+i; + im[idx] = (1 == msk->operator[](idx)) ? im[idx] - float(rWrk[j] * cWrk[i]) : 0.0; + } + } + } + } + + //@brief : subtract the 2d background from an image + //@param im : image to subtract background from + template + void BckgSub2D::subtract(uint8_t * const im) { + const Real offset = c / 2; + const uint8_t nVal = (uint8_t) (offset + Real(0.5)); + for(int j = 0; j < m_h; j++) { + for(int i = 0; i < m_w; i++) { + const size_t idx = j*m_w+i; + if(1 == msk->operator[](idx)) { + const int vNew = int(im[idx]) - (int)std::round(rWrk[j] * cWrk[i] - offset);//compute background + if(vNew < 0) im[idx] = 0; + else if (vNew > 0xFF) im[idx] = 0xFF; + else im[idx] = (uint8_t) vNew; + } else { + im[idx] = nVal; + } + } + } + } + + //@brief : subtract the 2d background from an image + //@param im : image to subtract background from + template + void BckgSub2D::subtract(uint16_t * const im) { + const Real offset = c / 2; + const uint16_t nVal = (uint16_t) (offset + Real(0.5)); + for(int j = 0; j < m_h; j++) { + for(int i = 0; i < m_w; i++) { + const size_t idx = j*m_w+i; + if(1 == msk->operator[](idx)) { + const int vNew = int(im[idx]) - (int)std::round(rWrk[j] * cWrk[i] - offset);//compute background + if(vNew < 0) im[idx] = 0; + else if (vNew > 0xFFFF) im[idx] = 0xFFFF; + else im[idx] = (uint16_t) vNew; + } else { + im[idx] = nVal; + } + } + } + } + + //@brief : out of place subtract the 2d background from an image + //@param im: image to subtract background from + //@param bf: location to write background subtracted image + template + template + void BckgSub2D::subtract(TPix const * const im, Real * const bf) { + for(int j = 0; j < m_h; j++) { + for(int i = 0; i < m_w; i++) { + const size_t idx = j*m_w+i; + bf[idx] = (1 == msk->operator[](idx)) ? Real(im[idx]) - rWrk[j] * cWrk[i] : Real(0); + } + } + } +} + +#endif//_GAUSSIAN_H_ diff --git a/include/util/image.hpp b/include/util/image.hpp new file mode 100644 index 0000000..47b8da1 --- /dev/null +++ b/include/util/image.hpp @@ -0,0 +1,719 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _IMAGE_H_ +#define _IMAGE_H_ + +#include +#include + +#include "fft.hpp" + +namespace image { + + //@brief : convert an image to 8 bit + //@param im : image to convert + //@param nPix: number of pixels + //@param p8 : location to write 8 bit image + //@note : intensities are rescaled so that [min,max] maps to [0,255] + template + void to8Bit(TPix * const im, const size_t nPix, uint8_t * const p8); + + //@brief : convert an image to 8 bit + //@param im : image to convert + //@param nPix: number of pixels + //@note : intensities are rescaled so that [min,max] maps to [0,255] + //@return : rescaled im + template + std::vector to8Bit(TPix * const im, const size_t nPix); + + //@brief : adaptive histogram equalization + //@param im : image to equalize + //@param w : width of image in pixels + //@param h : height of image in pixels + //@param nx : number of tiles (contrast regions) across image + //@param ny : number of tiles (contrast regions) down image + //@param vMin: minimum histogram value (NAN to compute from input image) + //@param vMax: maximum histogram value (NAN to compute from input image) + //@reference : Pizer, S. M., Amburn, E. P., Austin, J. D., Cromartie, R., Geselowitz, A., Greer, T., ... & Zuiderveld, K. (1987). Adaptive histogram equalization and its variations. Computer vision, graphics, and image processing, 39(3), 355-368. + //@note : this currently uses 'mosaic' sampling (see fig 3a) + //@note : NAN pixels are ignored for equalization and then replaced with 0 afterwards + template + void adHistEq(TPix * const im, const size_t w, const size_t h, const size_t nx, const size_t ny, double vMin = NAN, double vMax = NAN); + + //@brief : compute the histogram of an image + //@param im : image to compute histogram of + //@param w : width of image in pixels + //@param h : height of image in pixels + //@param bins: location to write values of histogram bins + //@param cnts: number of pixels in each bin + //@param nBin: number of histogram bins to use + template + void hist(TPix * const im, const size_t w, const size_t h, TPix * bins, size_t * cnts, const size_t nBin = 256); + + //@brief : compute the otsu threshold for an image from its hisogram + //@param bins: location to write values of histogram bins + //@param cnts: number of pixels in each bin + //@param nBin: number of histogram bins to use + //@return : index of threshold value in bins + template + size_t otsu(TPix * bins, size_t * cnts, const size_t nBin = 256); + + //@brief : compute image qualty of a spectrum + //@param dct: discrete cosine transform of image to compute IQ of + //@param w : width of image + //@param h : height of image + //@return : image quality + template + Real imageQuality(Real * dct, const size_t w, const size_t h); + + //@brief: helper for computing image quality + template + class ImageQualityCalc { + fft::vector < Real > wrk;//work space for frequency domain + std::shared_ptr< fft::DCT2D > fwd;//plan for forward DCT + public: + const size_t w, h;//width/height of image to compute image quality of + + //@brief : construct an image quality calculator + //@param w : width of images + //@param h : height of images + //@param flg: fft flag to use when creating DCT plans + ImageQualityCalc(const size_t w, const size_t h, const fft::flag::Plan flg = fft::flag::Plan::Measure) : w(w), h(h), fwd(std::make_shared >(w, h, flg, false)), wrk(w * h) {} + + //@brief : compute image quality of an image + //@param im: image to compute quality of + //@return : image quality + double compute(Real const * im) {fwd->execute((Real*)im, wrk.data()); return imageQuality(wrk.data(), w, h);} + }; + + //helper for bilinearly interpolation + template + struct BiPix { + size_t idx ;//index of pixel in square sphere grid + size_t inds[4];//index of 4 bounding pixels on detector + Real wgts[4];//relative weights of pixels to interpolate from + + //@brief : bilinearly interpolate a pattern at the spherical pixel + //@param pat: detector pattern to interpolate from + //@return : interpolated value + Real interpolate(Real const * const pat) const; + + //@brief : compute the indicies and coefficient to bilinearlly interpolate from an image + //@param x : fractional horizontal position in image [0,1] + //@param y : fractional vertical position in image [0,1] + //@param w : width of image in pixels + //@param h : height of image in pixels + void bilinearCoeff(Real x, Real y, const size_t w, const size_t h); + }; + + //helper for resacling images + template + class Rescaler { + fft::vector < Real > work ;//work space for frequency domain + std::shared_ptr< fft::DCT2D > fwd , inv ;//plan for forward/inverse DCT + public: + const size_t wIn , hIn ;//width/height of input image + const size_t wOut, hOut;//width/height of output image + + //@brief: construct a scaler to rescale images from wI x hI -> wO x hO + //@param wI : width of input images + //@param hI : height of input images + //@param wO : width of output images + //@param hO : height of output images + //@param flg: fft flag to use when creating DCT plans + Rescaler(const size_t wI, const size_t hI, const size_t wO, const size_t hO, const fft::flag::Plan flg); + + //@brief: construct a scaler to rescale images from wI x hI -> (wI x hI) * scl + //@param wI : width of input images + //@param hI : height of input images + //@param scl: scale factor + //@param flg: fft flag to use when creating DCT plans + Rescaler(const size_t wI, const size_t hI, const Real scl, const fft::flag::Plan flg) : Rescaler(wI, hI, (size_t)std::round(scl * wI), (size_t)std::round(scl * hI), flg) {} + + //@brief : rescale an image + //@param in : input image to rescale (wIn * hIn) + //@param out: location to write rescaled imaged (wOut * hOut), can be the same as in + //@param zer: true/false to make average of the image zero + //@param flt: width of high pass filter (0 to leave unfiltered) [dc value isn't affected] + //@param iq : should the image quality be computed + //@return : iq ? image quality : 0 + Real scale(Real* in, Real* out, bool zer = false, const size_t flt = 0, const bool iq = false) {return scale(in, out, work.data(), zer, flt, iq);} + + //@breif: allocate space large enough for the working array + fft::vector allocateWork() const {return fft::vector(work.size());} + + //@brief : rescale an image using a user provided work array + //@param in : input image to rescale (wIn * hIn) + //@param out: location to write rescaled imaged (wOut * hOut), can be the same as in + //@param wrk: work space (should be allocated via allocateWork()) + //@param zer: true/false to make average of the image zero + //@param flt: width of high pass filter (0 to leave unfiltered) [dc value isn't affected] + //@param iq : should the image quality be computed + //@return : iq ? image quality : 0 + Real scale(Real* in, Real* out, Real* wrk, bool zer = false, const size_t flt = 0, const bool iq = false) const; + }; + + //helper for subtracting backgrounds from non-rectangular images + template + class BlockRowBackground { + std::shared_ptr< std::vector< std::pair > > msk ;//range of pixels within circular mask for each row + std::shared_ptr< std::vector< size_t > > yCnt;//number of pixels in each column + const size_t w, h;//width/height of image to subtract background + std::shared_ptr< fft::DCT > xFwd;//plane for forward DCT of a single row + std::shared_ptr< fft::DCT > yFwd;//plane for forward DCT of a single column + std::shared_ptr< fft::DCT > xInv;//plane for inverse DCT of a single row + std::shared_ptr< fft::DCT > yInv;//plane for inverse DCT of a single column + fft::vector < Real > wrk1;//work space + fft::vector < Real > wrk2;//work space + fft::vector < Real > wrk3;//work space + + //@brief : private constructor to build a block row background subtractor from row blocks + size + //@param rMsk : row blocks (height is size) + //@param width: width of image + BlockRowBackground(std::vector< std::pair >& rMsk, const size_t width); + + public: + //@brief : construct a background subtractor for a circular mask inscribed in an image (centered) + //@param w: width of image + //@param h: height of image + //@return : background subtractor + static BlockRowBackground Circular(const size_t w, const size_t h); + + //@brief : subtract x and y backgrounds from inside the block region + //@param im: image to subtract background from + //@param x : width of high pass filter for x direction (0 to leave unfiltered) + //@param y : width of high pass filter for y direction (0 to leave unfiltered) + void subtract(Real * const im, const size_t x, const size_t y); + }; +} + +#include +#include +#include +#include + +#include "constants.hpp" + +namespace image { + //////////////////////////////////////////////////////////////////////// + // BiPix // + //////////////////////////////////////////////////////////////////////// + + //@brief : convert an image to 8 bit + //@param im : image to convert + //@param nPix: number of pixels + //@param p8 : location to write 8 bit image + //@note : intensities are rescaled so that [min,max] maps to [0,255] + template + void to8Bit(TPix * const im, const size_t nPix, uint8_t * const p8) { + //handle trivial case first + if(std::is_same::value) { + std::transform(im, im + nPix, p8, [](const TPix& v){return (uint8_t)v;});//casting needed to supress compiler warnings on nont same types + return; + } + + //compute minimum and maximum quality + std::pair minMax = std::minmax_element(im, im + nPix); + const TPix delta = *minMax.first; + + //rescale to 8 bit range + if(std::numeric_limits::is_integer) { + const double scale = double(255) / (*minMax.second - delta); + std::transform(im, im + nPix, p8, [&](const TPix& v){return (uint8_t)std::round(scale * (v - delta));}); + } else { + const TPix scale = TPix (255) / (*minMax.second - delta); + std::transform(im, im + nPix, p8, [&](const TPix& v){return (uint8_t)std::round(scale * (v - delta));}); + } + } + + //@brief : convert an image to 8 bit + //@param im : image to convert + //@param nPix: number of pixels + //@note : intensities are rescaled so that [min,max] maps to [0,255] + //@return : rescaled im + template + std::vector to8Bit(TPix * const im, const size_t nPix) { + // if(std::is_same::value) return std::vector(im, im+nPix);//handle trivial case + std::vector ret(nPix);//allocate output space + to8Bit(im, nPix, ret.data());//do conversion + return ret;//return converted image + } + + //@brief : adaptive histogram equalization + //@param im : image to equalize + //@param w : width of image in pixels + //@param h : height of image in pixels + //@param nx : number of tiles (contrast regions) across image + //@param ny : number of tiles (contrast regions) down image + //@param vMin: minimum histogram value (NAN to compute from input image) + //@param vMax: maximum histogram value (NAN to compute from input image) + //@reference : Pizer, S. M., Amburn, E. P., Austin, J. D., Cromartie, R., Geselowitz, A., Greer, T., ... & Zuiderveld, K. (1987). Adaptive histogram equalization and its variations. Computer vision, graphics, and image processing, 39(3), 355-368. + //@note : this currently uses 'mosaic' sampling (see fig 3a) + //@note : NAN pixels are ignored for equalization and then replaced with 0 afterwards + template + void adHistEq(TPix * const im, const size_t w, const size_t h, const size_t nx, const size_t ny, double vMin, double vMax) { + //first get histogram limits (minmax_element doesn't work with nans) + // const std::pair minMax = std::minmax_element(im, im + w * h);//get limits of image + // const double vMin = double(*minMax.first );//darkest pixel value + // const double vMax = double(*minMax.second);//brightest pixel value + if(std::isnan(vMin) || std::isnan(vMax)) { + const double vMin0 = vMin;//save initial vMin in case just vMax is NAN + const double vMax0 = vMax;//save initial vMax in case just vMin is NAN (not currently possible) + bool init = false; + vMin = vMax = im[0]; + for(size_t i = 0; i < w * h; i++) { + if(!std::isnan(im[i])) { + if(!init) { + vMin = vMax = im[i]; + init = true; + } else { + if(im[i] < vMin) vMin = im[i]; + if(im[i] > vMax) vMax = im[i]; + } + } + } + if(!std::isnan(vMin0)) vMin = vMin0; + if(!std::isnan(vMax0)) vMax = vMax0; + } + + //create bins for histograms + const size_t nBins = 256;//number of histogram bins + const double range = vMax - vMin;//get range + if(0.0 == range) return;//no contrast to equalize + const double delta = range / nBins;//get histogram spacing + std::vector vBins(nBins);//allocate histogram + for(size_t i = 0; i < nBins; i++) vBins[i] = vMin + delta * (i+1);//fill in bins + vBins.back() = vMax;//make sure rounding errors don't put the last bin below the max + + //determine tile size + const double tx = double(w) / nx;//x tile size in fractional pixels + const double ty = double(h) / ny;//y tile size in fractional pixels + const double hy = ty / 2;//y half tile size in fractional pixels + + //compute x tile assignments and fractional positions once + std::vector it(w);//which tile is each pixel in + std::vector il(w);//which grid point is to the left of each pixel + std::vector ir(w);//which grid point is to the right of each pixel + std::vector fx(w), cx(w); + for(size_t i = 0; i < w; i++) {//loop over columns + double f = double(i) / tx;//convert from pixels to fractional tiles + it[i] = (size_t)f;//save the tile this pixel is in + il[i] = (size_t)std::max(f - 0.5, 0.0 ); + ir[i] = (size_t)std::min(f + 0.5, double(nx-1)); + fx[i] = std::fmod(f - it[i] + 0.5, 1.0);//save progress between tile centers + cx[i] = 1.0 - fx[i];//save complement of fx[i] + } + + //allocate a histogram for each x tile + std::vector hist(nx * nBins);//histogram for current row of tiles + std::vector cdfPrev(nx * nBins);//cumulative histogram for previous row of tiles + std::vector cdfCur (nx * nBins);//cumulative histogram for current row of tiles + std::vector wrk(nBins);//work space for partial sum + + //loop over rows of tiles + for(size_t k = 0; k < ny; k++) { + //compute y coordinates of tile row bounds + const double yS = ty * k ;//where does this tile row start + const double yE = ty * (k+1);//where does the tile row end + + //convert y coordinates to nearest pixel + const size_t jS = (size_t) std::round(yS); + const size_t jE = (size_t) std::round(yE); + + //now loop over this tile computing the histogram for each x tile + std::fill(hist.begin(), hist.end(), 0);//initialize histograms + for(size_t j = jS; j < jE; j++) {//loop over rows within this tile + for(size_t i = 0; i < w; i++) {//loop over width of image + const TPix& v = im[j * w + i];//get pixel value + if(!std::isnan(v)) {//don't count NaN pixels + const auto iBin = std::lower_bound(vBins.cbegin(), vBins.cend(), v);//find the bin this pixel belongs to + const size_t b = std::distance(vBins.cbegin(), iBin);//convert to bin index + ++hist[it[i] * nBins + b];//increment bin in correct histogram of current tile + } + } + } + + //next convert from histograms to CDFs + for(size_t i = 0; i < nx; i++) { + // std::partial_sum(hist.cbegin() + i * nBins, hist.cbegin() + (i+1) * nBins, cdfCur.begin() + i * nBins);//raw cdf + std::partial_sum(hist.cbegin() + i * nBins, hist.cbegin() + (i+1) * nBins, wrk.begin());//raw cdf + std::transform(wrk.cbegin(), wrk.cend(), cdfCur.begin() + i * nBins, [](const size_t& sum){return (double)sum;}); + const double nrm = range / cdfCur[i * nBins + nBins - 1];//compute normalization to that CDF goes from [0, range] + if(0 == cdfCur[i * nBins + nBins - 1]) {//don't divide by zero + std::copy(vBins.begin(), vBins.end(), cdfCur.begin() + i * nBins);//entire window is nan, use linear ramp + } else {//normal case, adjusth histogram + std::for_each(cdfCur.begin() + i * nBins, cdfCur.begin() + (i+1) * nBins, [nrm, vMin](double& v){v = v * nrm + vMin;});//rescale histogram to [vMin, vMax] + } + } + + //handle first row of tiles specially + if(k == 0) cdfPrev = cdfCur;//just make previous row the same as this one for first row + + //now loop over the parts of the tile we have the bounding points for and apply equalization + const double yM = yS + hy;//mid point of this tile row + const double yMPrev = yS - hy;//mid point of previous tile row + const size_t jMPrev = k == 0 ? 0 : (size_t) std::round(yMPrev);//index of first row to compute values for + const size_t jM = k+1 == ny ? h : (size_t) std::round(yM );//index of last row to compute values for + for(size_t j = jMPrev; j < jM; j++) {//loop over rows (this is previous to current midpoint for normal rows but cliped/extened for first/last row) + const double y = double(j); + const double fy = std::min((double(j) - yMPrev) / ty, 1.0);//compute fractional progress between tiles (clamp to 1 for bottom half of last row) + const double cy = 1.0 - fy; + for(size_t i = 0; i < w; i++) {//loop over image columns + TPix& v = im[j * w + i];//get pixel value + if(!std::isnan(v)) {//don't change NaN pixels + const auto iBin = std::lower_bound(vBins.cbegin(), vBins.cend(), v);//find the bin this pixel belongs to + const size_t b = std::distance(vBins.cbegin(), iBin);//convert to bin index + // const double grid[4] = {//get values of histogram equalization using 4 neighboring grid points + double grid[4] = {//get values of histogram equalization using 4 neighboring grid points + cdfPrev[nBins * il[i] + b], + cdfPrev[nBins * ir[i] + b], + cdfCur [nBins * il[i] + b], + cdfCur [nBins * ir[i] + b] + }; + //bilinearly interpolate grid points + v = grid[0] * cy * cx[i] + + grid[1] * cy * fx[i] + + grid[2] * fy * cx[i] + + grid[3] * fy * fx[i]; + } else { + v = 0;//replace nans with 0 + } + } + } + cdfCur.swap(cdfPrev); + } + } + + //@brief : compute the histogram of an image + //@param im : image to compute histogram of + //@param w : width of image in pixels + //@param h : height of image in pixels + //@param bins: location to write values of histogram bins + //@param cnts: number of pixels in each bin + //@param nBin: number of histogram bins to use + template + void hist(TPix * const im, const size_t w, const size_t h, TPix * bins, size_t * cnts, const size_t nBin) { + //compute bins + if(256 == nBin && std::is_same::value) { + //handle 8 bit images specially + std::iota(bins, bins + 256, 0); + } else { + //get limits of image + const std::pair minMax = std::minmax_element(im, im + w * h); + const double vMin = double(*minMax.first );//darkest pixel value + const double vMax = double(*minMax.second);//brightest pixel value + + //convert to range + const double range = vMax - vMin;//get range + const double delta = range / nBin;//get histogram spacing + for(size_t i = 0; i < nBin; i++) bins[i] = vMin + delta * (i+1);//fill in bins + bins[nBin-1] = vMax;//make sure rounding errors don't put the last bin below the max + } + + //now loop over image filling histogram + const size_t nPix = w * h; + std::fill(cnts, cnts + nBin, 0); + for(size_t i = 0; i < nPix; i++) ++cnts[std::distance(bins, std::lower_bound(bins, bins + nBin, im[i]))]; + } + + //@brief : compute the otsu threshold for an image from its hisogram + //@param bins: location to write values of histogram bins + //@param cnts: number of pixels in each bin + //@param nBin: number of histogram bins to use + //@return : index of threshold value in bins + template + size_t otsu(TPix * bins, size_t * cnts, const size_t nBin) { + //compute number of pixels + const size_t numPix = std::accumulate(cnts, cnts + nBin, size_t(0)); + + //compute mean pixel + double ut = std::inner_product(bins, bins + nBin, cnts, 0.0) / numPix; + + //now compute optimum threshold + double wk = double(cnts[0]) / numPix;//w(k) + double uk = wk * bins[0] ;//mu(k) + double sMax = 0 ;//maximum sigma^2_b(k) + size_t kMax = 0 ;//index of sMak + for(size_t k = 1; k < nBin - 1; k++) { + const double pk = double(cnts[k]) / numPix;//convert from count to probability + wk += pk ;//update w(k) + uk += pk * bins[k];//update u(k)` + const double f = ut * wk - uk;//numerator prefactor + const double s2 = f * f / (wk * (1.0 - wk));//compute merit sigma^2_b(k) + if(s2 > sMax) {//is this a new best threshold? + sMax = s2;//save best value + kMax = k ;//save best index + } + } + return kMax; + } + + //@brief : compute image qualty of a spectrum + //@param dct: discrete cosine transform of image to compute IQ of + //@param w : width of image + //@param h : height of image + //@return : image quality + template + Real imageQuality(Real * dct, const size_t w, const size_t h) { + Real vIq(0), sumP(0); + const uint64_t numPix = w * h;//compute number of pixels + uint64_t sumW(0);//this is big enough to hold the sum of the weights of at least a ~55000^2 image without overflow + for(uint64_t j = 0; j < h; j++) {//loop over spectra rows + uint64_t j2 = j * j;//compute y distance from origin^2 + for(uint64_t i = 0; i < w; i++) {//loop over spectra columns + uint64_t r2 = j2 + i * i;//compute weighting for this position in spectrum (radius^2) + const Real p = std::fabs(dct[j * w + i]);//get power + vIq += p * r2;//accumulate numerator (radius weighted power) + sumP += p ;//accumlate power + sumW += r2;//accumulate weighting + } + } + const Real den = Real(sumW) * sumP / numPix; + if(sumP == Real(0)) vIq = 0;//if summed power is 0 there is no image (or only a DC value) + else vIq = Real(1) - vIq / den;//normalize by power + return vIq; + } + + //@brief : bilinearly interpolate a pattern at the spherical pixel + //@param pat: detector pattern to interpolate from + //@return : interpolated value + template + Real BiPix::interpolate(Real const * const pat) const { + Real v = 0; + for(size_t i = 0; i < 4; i++) v += pat[inds[i]] * wgts[i]; + return v; + } + + //@brief : compute the indicies and coefficient to bilinearlly interpolate from an image + //@param x : fractional horizontal position in image [0,1] + //@param y : fractional vertical position in image [0,1] + //@param w : width of image in pixels + //@param h : height of image in pixels + //@return : BiPix holding appropriate weights and indices + template + void BiPix::bilinearCoeff(Real x, Real y, const size_t w, const size_t h) { + //determine 4 bounding pixels + x *= w-1; y *= h-1; + const size_t indX0 = std::min((size_t)x, w-1); + const size_t indY0 = std::min((size_t)y, h-1); + const size_t indX1 = std::min(indX0+1, w - 1);//handle x == 1 + const size_t indY1 = std::min(indY0+1, h - 1);//handle y == 1 + + //convert to vectorized indices + inds[0] = indY0 * w + indX0; + inds[1] = indY0 * w + indX1; + inds[2] = indY1 * w + indX0; + inds[3] = indY1 * w + indX1; + + //compute fractional progress between bounding pairs (and complements) + Real wx1 = x - indX0; + Real wy1 = y - indY0; + Real wx0 = Real(1) - wx1; + Real wy0 = Real(1) - wy1; + + //convert to pixel weights + wgts[0] = wy0 * wx0; + wgts[1] = wy0 * wx1; + wgts[2] = wy1 * wx0; + wgts[3] = wy1 * wx1; + } + + //////////////////////////////////////////////////////////////////////// + // Rescaler // + //////////////////////////////////////////////////////////////////////// + + //@brief: construct a scaler to rescale images from wI x hI -> wO x hO + //@param wI : width of input images + //@param hI : height of input images + //@param wO : width of output images + //@param hO : height of output images + //@param flg: fft flag to use when creating DCT plans + template + Rescaler::Rescaler(const size_t wI, const size_t hI, const size_t wO, const size_t hO, const fft::flag::Plan flg) : + wIn (wI), + hIn (hI), + wOut(wO), + hOut(hO), + fwd (std::make_shared >(wI, hI, flg, false)), + inv (std::make_shared >(wO, hO, flg, true )), + work(std::max(wI, wO) * std::max(hI, hO)) {} + + //@brief : rescale an image + //@param in : input image to rescale (wIn * hIn) + //@param out: location to write rescaled imaged (wOut * hOut), can be the same as in + //@param wrk: work space (should be allocated via allocateWork()) + //@param zer: true/false to make average of the image zero + //@param flt: width of high pass filter (0 to leave unfiltered) [dc value isn't affected] + //@param iq : should the image quality be computed + //@return : iq ? image quality : 0 + template + Real Rescaler::scale(Real* in, Real* out, Real* wrk, bool zer, const size_t flt, const bool iq) const { + //compute dct of input image (and image quality if needed) + fwd->execute(in, wrk);//forward dct + Real vIq = iq ? imageQuality(wrk, wIn, hIn) : Real(0); + + //truncate/pad to new size in frequency domain + const size_t hCpy = std::min(hIn, hOut);//number of rows to copy + if(wOut <= wIn) {//width constant or decreasing + for(size_t j = 0; j < hCpy; j++) {//loop over rows to copy in order + std::copy(wrk + j * wIn, wrk + j * wIn + wOut, wrk + j * wOut);//copy row by row + } + } else {//width is increasing + for(size_t j = hCpy - 1; j < hCpy; j--) {//loop over rows to copy in reverse order + for(size_t i = wIn - 1; i < wIn; i--) wrk[j*wOut + i] = wrk[j*wIn + i]; + std::fill(wrk + j * wOut + wIn, wrk + j * wOut + wOut, Real(0));//pad row to width + } + } + if(hOut > hIn) {//height is increasing + std::fill(wrk + wOut * hIn, wrk + wOut * hOut, Real(0));//pad to height + } + + //filter and compute inverse dct + if(zer) wrk[0] = 0;//set dc value to 0 -> make mean value of image 0 + if(flt > 0) { + //filter out long wavelengths to subtract non dc background + for(size_t j = 0; j < flt; j++) { + for(size_t i = 0; i < flt; i++) { + const Real r = std::sqrt(Real(j*j+i*i)); + if(r <= flt && (i > 0 || j > 0)) { + const Real factor = std::cos((r / (2 * flt) + Real(0.5)) * emsphinx::Constants::pi); + wrk[j * wOut + i] *= factor * factor; + } + } + } + } + inv->execute(wrk, out);//inverse dct + return vIq; + } + + //@brief : private constructor to build a block row background subtractor from row blocks + size + //@param rMsk : row blocks (height is size) + //@param width: width of image + template + BlockRowBackground::BlockRowBackground(std::vector< std::pair >& rMsk, const size_t width) : + msk (std::make_shared > >(rMsk)), + yCnt(std::make_shared >(width)), + w (width ), + h (rMsk.size() ), + wrk1(std::max(w, h)), + wrk2(std::max(w, h)), + wrk3(std::max(w, h)), + xFwd(std::make_shared >(w, fft::flag::Plan::Measure, false)), + yFwd(std::make_shared >(h, fft::flag::Plan::Measure, false)), + xInv(std::make_shared >(w, fft::flag::Plan::Measure, true )), + yInv(std::make_shared >(h, fft::flag::Plan::Measure, true )) { + //compute number of pixels in each column + std::fill(yCnt->begin(), yCnt->end(), 0); + for(size_t j = 0; j < h; j++) { + const std::pair& range = msk->operator[](j); + for(size_t i = range.first; i < range.second; i++) { + ++yCnt->operator[](i); + } + } + } + + + //@brief : construct a background subtractor for a circular mask inscribed in an image (centered) + //@param w: width of image + //@param h: height of image + //@return : background subtractor + template + BlockRowBackground BlockRowBackground::Circular(const size_t w, const size_t h) { + std::vector< std::pair > mask(h); + const size_t dim = std::min(w, h) - 1; + const Real cutoff = Real(1) + Real(1) / (2 * dim); + for(size_t j = 0; j < h; j++) {//loop over rows + std::pair& range = mask[j]; + range.first = w; + range.second = w; + const Real y = Real(j * 2) / dim - 1;//[0,1] + for(int i = 0; i < w; i++) { + const Real x = Real(i * 2) / dim - 1;//[0,1] + const Real r = std::hypot(x, y); + if(r <= cutoff) {//inside circle + if(w == range.first) range.first = i;//if this is the first point inside the circle save the start + } else if(w != range.first) {//this point is outside the circle but after at least one pixel inside the circle + if(w == range.second) range.second = i;//save first point outside circle + } + } + } + return BlockRowBackground(mask, w); + } + + //@brief : subtract x and y backgrounds from inside the block region + //@param im: image to subtract background from + //@param x : width of high pass filter for x direction (0 to leave unfiltered) + //@param y : width of high pass filter for y direction (0 to leave unfiltered) + template + void BlockRowBackground::subtract(Real * const im, const size_t x, const size_t y) { + //initalize averages with zeros + std::fill(wrk1.begin(), wrk1.begin() + h, 0);//average of rows + std::fill(wrk2.begin(), wrk2.begin() + w, 0);//average of columns + + //loop over image accumulating region inside mask + for(size_t j = 0; j < h; j++) { + const std::pair& range = msk->operator[](j); + for(size_t i = range.first; i < range.second; i++) { + wrk1[j] += im[j * w + i];//accumulate value of row + wrk2[i] += im[j * w + i];//accumulate value of colummn + } + if(range.first != range.second) wrk1[j] /= range.second - range.first;//normalized to average (without dividing by 0) + } + for(size_t i = 0; i < w; i++) if(0 != yCnt->operator[](i)) wrk2[i] /= yCnt->operator[](i);//normalized to average (without dividing by 0) + + //now we have the average of each row/column within the masked region, convert to background with low pass filter + xFwd->execute(wrk1.data(), wrk3.data()); + std::fill(wrk3.begin() + x, wrk3.end(), 0); + xInv->execute(wrk3.data(), wrk1.data()); + yFwd->execute(wrk2.data(), wrk3.data()); + std::fill(wrk3.begin() + y, wrk3.end(), 0); + if(x > 0) wrk3[0] = 0;//don't subtract DC value twice + yInv->execute(wrk3.data(), wrk2.data()); + + //correct for FFTW scaling + for(size_t j = 0; j < h; j++) wrk1[j] /= h * 2; + for(size_t i = 0; i < w; i++) wrk2[i] /= w * 2; + + //finally subtract background + for(size_t j = 0; j < h; j++) { + const std::pair& range = msk->operator[](j);//get range of good pixels for this row + for(size_t i = 0 ; i < range.first ; i++) im[j * w + i] = 0;//fill pxiels before mask with zero + for(size_t i = range.first ; i < range.second; i++) im[j * w + i] -= wrk1[j] + wrk2[i];//subtract background + for(size_t i = range.second; i < w ; i++) im[j * w + i] = 0;//fill pixels after mask with zero + } + } +} + +#endif//_IMAGE_H_ diff --git a/include/util/linalg.hpp b/include/util/linalg.hpp new file mode 100644 index 0000000..82c3e29 --- /dev/null +++ b/include/util/linalg.hpp @@ -0,0 +1,548 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019, William C. Lenthe * + * All rights reserved. * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _LINALG_H_ +#define _LINALG_H_ + +#include //size_t + +// this header implements a minimal set of matrix decompositions preferring compactness and readability over speed +// it should only be used if you only need to do a few decompositions and/or the matrix size is small +// if you need more obscure decompositions and/or speed is crucial use a proper library (e.g. Eigen or LAPACK) + +//@brief: functions to solve a linear system +//@note : these are convenience methods combining a decompse and backsolve call +//@note : here is the decision tree that matlab uses to solve dense systems of linear equations + /* + if(square) { + if([permuted] triangular) { + [p]tri + } else { + if(hermitian) {// conjugate transpose == self + if(diagonal is single sign) + try {cholesky} catch (...) {ldl} + else + ldl + } else {//not symmetric + if(upper hessenberg) + hess(not implemeneted) + else + lu + } + } + } else { + qr + } + */ +//@note: the following solvers aren't implemented here (but lu can be used for all of them with a modest speed penalty): +// [p]tri - trivial +// hess - not a decomposition, if you want to implemented the solver see +// Henry, G. (1994). The shifted hessenberg system solve computation. Cornell Theory Center, Cornell University. +// ldl - this is tricky to implement (unlike cholesky stability requires [2x2] pivoting), see +// Ashcraft, C., R.G. Grimes, and J.G. Lewis. “Accurate Symmetric Indefinite Linear Equations Solvers.” SIAM J. Matrix Anal. Appl. Vol. 20. Number 2, 1998, pp. 513–561. +namespace solve { + //@brief : solve the linear system of equations A * x = b for x + //@param a : square matrix A in row major order, destroyed during function (over written with LU) + //@param x : location to write x + //@param b : column vector b + //@param n : size of matrix + //@template Real: matrix scalar type, must be a floating point type (real or std::complex) + template void lu(Real * const a, Real * const x, Real const * const b, const size_t n); + + //@brief : solve the linear system of equations A * x = b for x + //@param a : square matrix A in row major order (only upper triangle is read, lower triangle is destroyed during function, overwritten with subdiagonal of L) + //@param x : location to write x + //@param b : column vector b + //@param n : size of matrix + //@template Real: matrix scalar type, must be a floating point type (real or std::complex) + //@note : matrix A must be symmetric positive-definite (real symmetric is a subset) + template void cholesky(Real * const a, Real * const x, Real const * const b, const size_t n); +} + +//@brief: functions to decompose a matrix +namespace decompose { + //@brief : perform in place LU decomposition + //@param a : square matrix A in row major order + //@param p : location to write pivots (compressed permutation matrix) [must hold at least n elements] + //@param n : size of matrix + //@template Real: matrix scalar type, must be a floating point type (real or std::complex) + //@note : input is destroyed during function (over written with LU) + template void lu(Real * const a, size_t * const p, const size_t n); + + //@brief : perform semi in place cholesky decomposition + //@param a : square matrix A in row major order + //@param d : location to write diagonal of L + //@param n : size of matrix + //@template Real: matrix scalar type, must be a floating point type (real or std::complex) + //@return : true if matrix was negated to decompose (all negative diagonal) + //@note : only upper triangle is read + //@note : sub diagonal components are destroyed during function (overwritten with subdiagonal of L) + template bool cholesky(Real * const a, Real * const d, const size_t n); + + //@brief : perform in place qr decomposition + //@param a : rectangular matrix a A in row major order + //@param m : number of rows + //@param n : number of columns + //@template Real: matrix scalar type, must be a floating point type (real or std::complex) + //@note : upper triangle of A is overwritten with R + // remainder of A is overwritten with Q in a compressed format (sequence of givens rotations) + //@note : Givens rotation algorithm 5.2.2 in section 5.2.3 of Golub and Van Loan (1996) Matrix Computations + template void qr(Real * const a, const size_t m, const size_t n); +} + +//@brief: functions to solve a linear system of equations using an existing decomposition +//@note : may be preferred over solve:: functions if the same matrix A needs to be solved for multiple x/b values +namespace backsolve { + //@brief : solve the linear system of equations A * x = b for x using LU decomposition of A (from decompose::lu) + //@param lu : lu decomposition of A + //@param p : pivots (compressed permutation matrix) + //@param x : location to write x + //@param b : column vector b + //@param n : size of matrix + //@template Real: matrix scalar type, must be a floating point type (real or std::complex) + //@note : input is destroyed during function (over written with LU) + template void lu(Real * const lu, size_t const * const p, Real * const x, Real const * const b, const size_t n); + + //@brief : solve the linear system of equations A * x = b for x using cholesky decomposition of A (from decompose::cholesky) + //@param ll : subdiagonal of cholesky decomposition of A in row major order + //@param d : diagonal of cholesky decomposition of A + //@param x : location to write x + //@param b : column vector b + //@param n : size of matrix + //@param neg : true/false if A did / didn't need to be negated for decomposition + //@template Real: matrix scalar type, must be a floating point type (real or std::complex) + //@note : only upper triangle is read + //@note : sub diagonal components are destroyed during function (overwritten with subdiagonal of L) + template void cholesky(Real const * const ll, Real const * const d, Real * const x, Real const * const b, const size_t n, const bool neg); +} + +//@brief: additional functions to use the result of qr +namespace qr { + //@brief : compute the product of Q with another matrix y + //@param a : qr decomposition of a (from decompose::qr) + //@param y : matrix to multiply Q by + //@param m : number of rows in a (and y) + //@param n : number of columns in a + //@param p : number of columns in y + //@template Real: matrix scalar type, must be a floating point type (real or std::complex) + //@note : y is overwritten with Q * y + //@note : Q can be 'decompressed' by calling this function with y == Identity(m) ( && p == m) + template void applyQ(Real const * const a, Real * const y, const size_t m, const size_t n, const size_t p); + + //@brief : compute the product of Q^H (==Q^-1) with another matrix y + //@param a : qr decomposition of a (from decompose::qr) + //@param y : matrix to multiply Q^H by + //@param m : number of rows in a (and y) + //@param n : number of columns in a + //@param p : number of columns in y + //@template Real: matrix scalar type, must be a floating point type (real or std::complex) + //@note : y is overwritten with Q^H * y + template void applyQH(Real const * const a, Real * const y, const size_t m, const size_t n, const size_t p); +} + +//////////////////////////////////////////////////////////////////////// +// Implementations // +//////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//@brief: helper functions to handle real and complex valued decompositions with a single function +namespace detail { + //helper to check if a type is std::complex + template struct is_complex : std::false_type {};//default to 'naked' real number + template struct is_complex > : std::true_type {};//handle std::complex specially, can get base type as typename Real::value_type + + //shortcuts for enable_if + template using IfReal = typename std::enable_if::value, int>::type; + template using IfCplx = typename std::enable_if< is_complex::value, int>::type; + + //helper to typedef real value from a real or complex template + template struct ReVal; + template struct ReVal::value >::type> {typedef R type;};//ReVal::type == Real + template struct ReVal::value >::type> {typedef typename C::value_type type;};//ReVal::type == typename Cplx::value_type + + //helper to check if real part of number is negative + template = 0> inline bool signbit(const Real v) {return std::signbit(v );} + template = 0> inline bool signbit(const Cplx v) {return std::signbit(v.real());} + + //helper to get machine epsilon + template = 0> inline Real eps () {return std::numeric_limits< Real >::epsilon();} + template = 0> inline typename Cplx::value_type eps () {return std::numeric_limits::epsilon();} + + //helper to return conjugate of real number os real (instead of complex) + template = 0> inline Real conj (const Real v) {return v ;} + template = 0> inline Cplx conj (const Cplx v) {return std::conj(v);} +} + +namespace qr { + //compute, compress, and decompress a givens rotation + namespace givens { + //@brief: compute the givens rotation to zero one element of a matrix + //@param va: entry to rotate into + //@param vb: entry to rotate from (will be zeroed) + //@param c : location to write cosine term of givens rotation + //@param s : location to write sine term of givens rotation + //@return : true if a rotation is needed, false if |vb| is already ~0 + template = 0> + bool compute(const Real& va, const Real& vb, Real& c, Real& s) { + //this method works for complex numbers as well if va/vb are replaces with |va|, |vb| but isn't ideal + if(std::sqrt(std::norm(vb)) < detail::eps()) {//vb is already 0 + c = Real(1); + s = Real(0); + return false; + } else {//vb is nonzero + const Real h = std::hypot(va, vb);//vb is nonzero so no chance to divide by 0 + c = va / h;//cos of rotation to zero vb + s = vb / h;//sin of rotation to zero vb + return true; + } + } + + //@brief: compute the givens rotation to zero one element of a matrix + //@param va: entry to rotate into + //@param vb: entry to rotate from (will be zeroed) + //@param c : location to write cosine term of givens rotation + //@param s : location to write sine term of givens rotation + //@return : true if a rotation is needed, false if |vb| is already ~0 + template = 0> + bool compute(const Cplx& va, const Cplx& vb, Cplx& c, Cplx& s) { + //a complex givens rotation to zero vb into va can be computed analogously to a real givens rotation: + // h = sqrt( |va|^2 + |vb|^2 ) + // c = va / h + // s = vb / h + //if va and vb are considered as complex numbers va == ma * exp(I * pa) where ma/mb are the magnitudes and pa/pb the phases + //then the givens rotation is essentially a combination of 3 rotations + // c = cos(theta) * exp(I * -pa) + // s = sin(theta) * exp(I * -pb) + // theta = atan( |mb| / |ma| ) + //this is the easiest option but leaves us 3 degrees of freedom to store as 2 numbers for an in place decomposition + //also the resulting givens matrix isn't hermetian (since c.imag() is non-zero) + //the givens rotation can also be considered as a combination of 2 rotations e.g. + // c = cos(theta) + // s = sin(theta) * exp(I * phi) + // phi = pa - pb + //this also works for real numbers its just slightly more expensive (and confusing) + const typename Cplx::value_type mb = std::sqrt(std::norm(vb));//get magnitude of element to zero + if(mb < detail::eps()) {//vb is already 0 (theta == 0) + c = Cplx(1); + s = Cplx(0); + return false; + } else {//vb is nonzero + const typename Cplx::value_type ma = std::sqrt(std::norm(va));//get magnitude of element to zero into + if(ma < detail::eps()) {//don't divide by 0 (theta == pi/2) + c = typename Cplx::value_type(0);//cos(theta) + s = detail::conj(vb) / mb; + } else { + typename Cplx::value_type st(1); + const typename Cplx::value_type ba = mb / ma;//we need to check that vb isn't already zero to avoid divide by 0 here + c = st / std::hypot(ba, st);//cos(theta) + st = c.real() * ba; //sin(theta) + s = (va * detail::conj(vb)) * ( st / (ma * mb) );// cos/sin(phi) * sin(theta) + } + return true; + } + } + + //@brief : compress a givens rotation into single number + //@param c: cos(theta) + //@param s: sin(theta) + //@return : 'compressed' representation of c and s + //@note : based on section 5.1.11 of Golub and Van Loan (1996) Matrix Computations + template = 0> + Real compress(const Real& c, const Real& s) { + if(std::fabs(c) < std::numeric_limits::epsilon()) {//don't divide by zero + return Real(std::signbit(s) ? -1 : 1);//cos == 0 -> store sign of sine + } else if(std::signbit(c) == std::signbit(s)) {//0 < theta < 90 or 180 < theta < 270 + return s / Real(2);//save sin / 2 -> [-0.5, 0.0), (0.0,0.5] + } else {//90 < theta < 180 or 270 < theta < 360 + return Real(2) / c;//save 2 / c (-inf, -2], [2, inf) + } + } + + //@brief : compress a givens rotation into single number + //@param c: cos(theta) + //@param s: sin(theta) * exp(I * phi) + //@return : 'compressed' representation of c and s + //@note : modified to handle complex givens rotations + template = 0> + Cplx compress(const Cplx& c, const Cplx& s) { + const Cplx ePhi = s / std::sqrt(typename Cplx::value_type(1) - c.real() * c.real());//exp(I * phi) * sign( cos(theta) ) + return Cplx(c.real(), compress(ePhi.real(), ePhi.imag()));//leave s as is, compress cos/sin(phi) using real valued function + } + + //@brief : decompress a givens rotation back into a sin/cos pair + //@param p: compressed rotation (output of compress) + //@param c: location to write cos(theta) + //@param s: location to write sin(theta) + //@note : based on section 5.1.11 of Golub and Van Loan (1996) Matrix Computations + template = 0> + void decompress(const Real& p, Real& c, Real& s) { + const Real ap = std::fabs(p); + if(Real(1) == ap) {// +/-1 means we stored sine + c = Real(0); + s = p; + } else if(ap < Real(1)) {//p is s/2 and signs match + s = Real(2) * p;//extract sine + c = std::copysign(std::sqrt(Real(1) - s * s), p);//mag from s^2+c^2==1 and sign from p (match) + } else {//p is 2/c and signs don't match + c = Real(2) / p;//extract cosine + s = std::copysign(std::sqrt(Real(1) - c * c),-p);//mag from s^2+c^2==1 and sign from p (mismatch) + } + } + + //@brief : decompress a givens rotation + //@param p: compressed rotation (output of compress) + //@param c: location to write cos(theta) + //@param s: location to write sin(theta) * exp(I * phi) + //@note : modified to handle complex givens rotations + template = 0> + void decompress(const Cplx& p, Cplx& c, Cplx& s) { + typename Cplx::value_type sr, si; + c = Cplx(p.real(), typename Cplx::value_type(0));//c is already real valued + decompress(p.imag(), sr, si);//extract exp(I * phi) * sign( sin(theta) ) + s = Cplx(sr, si) * std::sqrt(typename Cplx::value_type(1) - c.real() * c.real());//multiply by |sin(theta)| + } + } +} + +namespace solve { + //@brief : solve the linear system of equations A * x = b for x + //@param a : square matrix A in row major order, destroyed during function (over written with LU) + //@param x : location to write x + //@param b : column vector b + //@param n : size of matrix + //@template Real: matrix scalar type, must be a floating point type (real or std::complex) + template void lu(Real * const a, Real * const x, Real const * const b, const size_t n) { + std::vector p(n);//allocate storage for pivots + decompose::lu(a, p.data(), n);//decompose in place + backsolve::lu(a, p.data(), x, b, n);//backsolve + } + + //@brief : solve the linear system of equations A * x = b for x + //@param a : square matrix A in row major order (only upper triangle is read, lower triangle is destroyed during function, overwritten with subdiagonal of L) + //@param x : location to write x + //@param b : column vector b + //@param n : size of matrix + //@template Real: matrix scalar type, must be a floating point type (real or std::complex) + //@note : matrix A must be symmetric positive-definite (real symmetric is a subset) + template void cholesky(Real * const a, Real * const x, Real const * const b, const size_t n) { + std::vector d(n);//allocate memory to hold diagonal (so we don't have to overwrite a's diagonal) + const bool neg = decompose::cholesky(a, d.data(), n);//decompose (semi) in place + backsolve::cholesky(a, d.data(), x, b, n, neg);//backsolve + } +} + +namespace decompose { + //@brief : perform in place LU decomposition + //@param a : square matrix A in row major order + //@param p : location to write pivots (compressed permutation matrix) [must hold at least n elements] + //@param n : size of matrix + //@template Real: matrix scalar type, must be a floating point type (real or std::complex) + //@note : input is destroyed during function (over written with LU) + template void lu(Real * const a, size_t * const p, const size_t n) { + static_assert(std::is_floating_point::type>::value, "solve::lu must be tempalted on either a real or complex type"); + //compute 1 / max(|row|) for implicitly scaled pivoting + std::vector s(n, 1);//scaling factor + for(size_t i=0; i())//if the biggest number is too small the matrix is singular + throw std::runtime_error("singular matrix");//stop! (could handle but why bother if we can't back solve) + + //pivot if needed + if(i != iMax) { + std::swap_ranges(a+i*n, a+(i+1)*n, a+iMax*n);//swap row n with row iMax + std::swap(s[i], s[iMax]);//swap row scalings + std::swap(p[i], p[iMax]);//update permutation + } + + //reduce + for(size_t j = i+1; j < n; j++) { + a[j*n+i] /= a[i*n+i]; + const Real& aji = a[j*n+i];//we'll need this value a bunch of times + for(size_t k = i+1; k < n; k++) a[j*n+k] -= aji * a[i*n+k]; + } + } + } + + //@brief : perform semi in place cholesky decomposition + //@param a : square matrix A in row major order + //@param d : location to write diagonal of L + //@param n : size of matrix + //@template Real: matrix scalar type, must be a floating point type (real or std::complex) + //@return : true if matrix was negated to decompose (all negative diagonal) + //@note : only upper triangle is read + //@note : sub diagonal components are destroyed during function (overwritten with subdiagonal of L) + template bool cholesky(Real * const a, Real * const d, const size_t n) { + static_assert(std::is_floating_point::type>::value, "cholesky must be templated on either a real or complex type"); + if(n == 0) return false;//handle empty matrix + const bool neg = detail::signbit(a[0]);//try negating matrix to handle negative definite matrices + for(size_t i = 0; i < n; i++) {//loop over rows + if(detail::signbit(a[n * i + i]) != neg) throw std::runtime_error("cholesky decomposition requires all positive or negative diagonal");//need to use LDL instead (which is why we've kept the diagonal intact) + for(size_t j = i; j < n; j++) {//loop over columns + Real sum = neg ? -detail::conj(a[n * i + j]) : detail::conj(a[n * i + j]);//get A_{i,j} (or negative if needed) + for(size_t k = 0; k < i; k++) sum -= detail::conj(a[i * n + k]) * a[j * n + k];//accumulate A_{i,j} - \sum_{k=0}^{j-1} L_{i,k} * L^*_{j,k} + if(i == j) {//we're computing L_{j,j} (save in d instead of overwriting elements of A) + //sum will always be real here since A_{i,i} is real and L_{i,k} * L^*_{j,k} -> L_{j,k} * L^*_{j,k} for i == j + if(std::real(sum) < detail::eps()) throw std::runtime_error("cholesky decomposition failed");//need to use LDL instead (which is why we've kept the diagonal intact) + d[i] = std::sqrt(sum);//L_{j,j} = \sqrt{ A_{j,j} - \sum_{k=0}^{j-1} L_{j,k} * L_{j,k} } + } else {//we're computing L_{i,j} + a[j * n + i] = sum / d[i];//L_{i,j} = ( A_{i,j} - \sum_{k=0}^{j-1} L_{i,k} * L_{j,k} ) / L_{j,j} + } + } + } + return neg; + } + + //@brief : perform in place qr decomposition + //@param a : rectangular matrix a A in row major order + //@param m : number of rows + //@param n : number of columns + //@template Real: matrix scalar type, must be a floating point type (real or std::complex) + //@note : upper triangle of A is overwritten with R + // remainder of A is overwritten with Q in a compressed format (sequence of givens rotations) + //@note : Givens rotation algorithm 5.2.2 in section 5.2.3 of Golub and Van Loan (1996) Matrix Computations + template void qr(Real * const a, const size_t m, const size_t n) { + for(size_t i = 0; i < n; i++) {//loop over columns (left to right) + for(size_t j = i+1; j < m; j++) {//loop down rows (top to bottom) + const Real& vb = a[j*n+i];//this is the element we're zeroing + const Real& va = a[i*n+i];//this is the element we're rotating with (I chose the diagonal but that is a bit arbitrary) + Real s, c;//location to save givens rotation + if(qr::givens::compute(va, vb, c, s)) {//get the givens rotation to zero out vb (and check if vb is already 0) + for(size_t k = i; k < n; k++) {//loop over row applying rotation (skip columns that are already zeroed) + const Real ak = a[i*n+k];//element from row we're rotating into + const Real bk = a[j*n+k];//element from row rotating out of + a[i*n+k] = s * bk + c * ak;//apply rotation + a[j*n+k] = detail::conj(c) * bk - detail::conj(s) * ak;//apply rotation + } + } + a[j*n+i] = qr::givens::compress(c, s);//save the rotation (accumulate elements of Q) + } + } + } +} + +//@brief: functions to solve a linear system of equations using an existing decomposition +//@note : may be preferred over solve:: functions if the same matrix A needs to be solved for multiple x/b values +namespace backsolve { + //@brief : solve the linear system of equations A * x = b for x using LU decomposition of A (from decompose::lu) + //@param lu : lu decomposition of A + //@param p : pivots (compressed permutation matrix) + //@param x : location to write x + //@param b : column vector b + //@param n : size of matrix + //@template Real: matrix scalar type, must be a floating point type (real or std::complex) + //@note : input is destroyed during function (over written with LU) + template void lu(Real * const lu, size_t const * const p, Real * const x, Real const * const b, const size_t n) { + for(size_t i = 0; i < n; i++) x[i] = b[p[i]];//permute b + for(size_t i = 0; i < n; i++) x[i] -= std::inner_product(x, x+i, lu+i*n, Real(0));//solve L y = b for y + for(size_t i = n-1; i < n; i--) x[i] = (x[i] - std::inner_product(x+i+1, x+n, lu+i*n+i+1, Real(0))) / lu[i*n+i];//solve U x = y for x + } + + //@brief : solve the linear system of equations A * x = b for x using cholesky decomposition of A (from decompose::cholesky) + //@param ll : subdiagonal of cholesky decomposition of A in row major order + //@param d : diagonal of cholesky decomposition of A + //@param x : location to write x + //@param b : column vector b + //@param n : size of matrix + //@param neg : true/false if A did / didn't need to be negated for decomposition + //@template Real: matrix scalar type, must be a floating point type (real or std::complex) + //@note : only upper triangle is read + //@note : sub diagonal components are destroyed during function (overwritten with subdiagonal of L) + template void cholesky(Real const * const ll, Real const * const d, Real * const x, Real const * const b, const size_t n, const bool neg) { + for(size_t i = 0; i < n; i++) x[i] = (b[i] - std::inner_product(x, x + i, ll + i * n, Real(0))) / d[i];//solve L y = b for y with back substitution + for(size_t i = n-1; i < n; i--) {//solve L^* x = y for x with forward substitution + for(size_t j = n-1; j != i; --j) x[i] -= detail::conj(ll[j * n + i]) * x[j]; + x[i] /= d[i]; + } + if(neg) std::transform(x, x + n, x, std::negate());//undo negation if needed + } +} + +//@brief: additional functions to use the result of qr +namespace qr { + //@brief : compute the product of Q with another matrix y + //@param a : qr decomposition of a (from decompose::qr) + //@param y : matrix to multiply Q by + //@param m : number of rows in a (and y) + //@param n : number of columns in a + //@param p : number of columns in y + //@template Real: matrix scalar type, must be a floating point type (real or std::complex) + //@note : y is overwritten with Q * y + //@note : Q can be 'decompressed' by calling this function with y == Identity(m) ( && p == m) + template void applyQ(Real const * const a, Real * const y, const size_t m, const size_t n, const size_t p) { + for(size_t i = n-1; i < n; i--) {//loop over columns (right to left) [reverse order from calculation] + for(size_t j = m-1; j > i; j--) {//loop up rows (bottom to top) [reverse order from calculation] + Real c, s;//storage for components of rotation + qr::givens::decompress(a[j*n+i], c, s);//extract rotation that was applied + for(size_t k = 0; k < p; k++) {//loop over columns of y + const Real ak = y[j*p+k];//element from current row [swapped from calculation] + const Real bk = y[i*p+k];//element from diagonal row [swapped from calculation] + y[j*p+k] = detail::conj(s) * bk + detail::conj(c) * ak;//apply rotation + y[i*p+k] = c * bk - s * ak;//apply rotation + } + } + } + } + + //@brief : compute the product of Q^H (==Q^-1) with another matrix y + //@param a : qr decomposition of a (from decompose::qr) + //@param y : matrix to multiply Q^H by + //@param m : number of rows in a (and y) + //@param n : number of columns in a + //@param p : number of columns in y + //@template Real: matrix scalar type, must be a floating point type (real or std::complex) + //@note : y is overwritten with Q^H * y + //@warning : this is currently actually Q^T but for complex number the ivners of Q is Q^* (conjugate transpose) + template void applyQH(Real const * const a, Real * const y, const size_t m, const size_t n, const size_t p) { + for(size_t i = 0; i < n; i++) {//loop over columns (left to right) [same order as calculation] + for(size_t j = i+1; j < m; j++) {//loop down rows (top to bottom) [same order as calculation] + Real c, s;//storage for components of rotation + qr::givens::decompress(a[j*n+i], c, s);//extract rotation that was applied + for(size_t k = 0; k < p; k++) {//loop over columns of y + const Real ak = y[(i-0)*p+k];//element from diagonal row [same as calculation] + const Real bk = y[(j )*p+k];//element from current row [same as calculation] + y[(i-0)*p+k] = s * bk + c * ak;//apply rotation + y[(j )*p+k] = detail::conj(c) * bk - detail::conj(s) * ak;//apply rotation + } + } + } + } +} + +#endif//_LINALG_H_ diff --git a/include/util/nml.hpp b/include/util/nml.hpp new file mode 100644 index 0000000..15a7c9a --- /dev/null +++ b/include/util/nml.hpp @@ -0,0 +1,581 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _NML_H_ +#define _NML_H_ + +#include +#include +#include +#include + +#define NML_USE_H5 +#ifdef NML_USE_H5 + #include "H5Cpp.h" +#endif + +namespace nml { + enum class Type { + None , + Bool , + Int , + Double, + String + }; + + //a (bad) variant class as a place holder until I can use std::variant (c++17) + //could probably use a union here but the memory overhead should be minimal right now + struct Variant { + //@brief: construct an empty variant + Variant() {clear();} + + //@brief: construct a variant from a value + template Variant(const T& v) {set(v);} + + //@brief: clear the stored type + void clear() {t = Type::None; f = true;} + + //@brief : get the the stored value + //@return: stored value + //@note : throws if the return type isn't the same as (or nicely castable to) the stored type (e.g. can't get if t != Double) + bool getBool (); + int getInt (); + double getDouble(); + std::string getString(); + + //@brief : set the stored value and update the type if needed + //@param v: value to store + template void set(const T& v); + + //@brief: get the stored type + Type getType() const {return t;} + + //@brief : check if the value has been read via get() since setting + //@return: true/false if the value has / hasn't been read + //@note : always returns true if Type is None + bool used() const {return f;} + + //@brief : convert the currently held value to a string representatino + //@return: string representation + std::string to_string() const; + + private: + //storage for value + union { + bool b; + int i; + double d; + } u;//at least putting these in a union save a bit of memory + std::string s; + + Type t;//flag for stored value type + bool f;//flag for if the value has been read since setting + }; + + //a token is a name value pair with the value a vector of variants + struct Value : public std::vector { + //@brief : get the type of the currently stored value + Type getType() const {return this->empty() ? Type::None : this->front().getType();} + + //@brief : check if the current value has been read + //@return: true if the current value was read since setting, false otherwise + bool wasUsed() const;// {return std::all_of(this->cbegin(), this->cend(), [](const Variant& v) {return v.used();});} + }; + + //a namelist is a collection of key/value pairs (with case insensitive keys) + class NameList : private std::map { + std::vector fileLines;//original file + + //@brief : convert a string to lower case + //@param s: string to convert + //@return : lowercase string + static std::string ToLower(std::string s);// {std::transform(s.begin(), s.end(), s.begin(), [](char c){return std::tolower(c);}); return s;} + + //@brief : find the first token with a given key + //@param nm : key to search for + //@return : token with specified key (throws if not found) + //@note : write acces isn't public facing + Value & at(const std::string nm); + + public: + //@brief: construct an empty namelist + NameList() {} + + //@brief : construct a namelist from a file + //@param nm: file name to read from + NameList(const std::string nm) {read(nm);} + + //@brief : read a namelist file + //@param is: istream to read namelist from + //@param cm: comment characters (ignore lines if the first character after white space is in cm) + void read(std::istream& is, std::string cm = "!"); + + //@brief : read a namelist file + //@param nm: filename of namelist to read + //@param cm: comment characters (ignore lines if the first character after white space is in cm) + void read(std::string nm, std::string cm = "!"); + + //@brief : find the first token with a given key + //@param nm : key to search for + //@return : token with specified key (throws if not found) + Value const& at(const std::string nm) const; + + //@brief : attempt to parse a bool from a list of namelist parameters + //@param nm : key to search for + //@return : parsed bool (throws if not found) + bool getBool (std::string nm) {return at(nm)[0].getBool ();} + + //@brief : attempt to parse a int from a list of namelist parameters + //@param nm : key to search for + //@return : parsed int (throws if not found) + int getInt (std::string nm) {return at(nm)[0].getInt ();} + + //@brief : attempt to parse a double from a list of namelist parameters + //@param nm : key to search for + //@return : parsed double (throws if not found) + double getDouble(std::string nm) {return at(nm)[0].getDouble();} + + //@brief : attempt to parse a string from a list of namelist parameters + //@param nm : key to search for + //@return : parsed string (throws if not found) + std::string getString(std::string nm) {return at(nm)[0].getString();} + + //@brief : attempt to parse a list fo bool from a list of namelist parameters + //@param nm : key to search for + //@return : parsed bools (throws if not found) + std::vector getBools (std::string nm); + + //@brief : attempt to parse a list fo int from a list of namelist parameters + //@param nm : key to search for + //@return : parsed ints (throws if not found) + std::vector getInts (std::string nm); + + //@brief : attempt to parse a list fo double from a list of namelist parameters + //@param nm : key to search for + //@return : parsed doubles (throws if not found) + std::vector getDoubles(std::string nm); + + //@brief : attempt to parse a list fo string from a list of namelist parameters + //@param nm : key to search for + //@return : parsed strings (throws if not found) + std::vector getStrings(std::string nm); + + //@brief : check if there are unused tokens + //@return: false if all tokens have been parse (via get___), false otherwise + bool fullyParsed() const; + + //@brief : get a list of unused tokens + //@return: names of all unused tokens + std::string unusedTokens() const; + + #ifdef NML_USE_H5 + //@brief : write all parsed tokens to an hdf file + //@param grp: hgf group to write values to + void writeParameters(H5::Group grp); + + //@brief : write the raw file to a dataset + //@param grp: hgf group to create dataset in + //@param nm : name of dataset to write + void writeFile(H5::Group grp, std::string nm) const; + #endif + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// Implementation Details // +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +namespace nml { + + bool Variant::getBool () {if(t == Type::Bool ) {f = true; return u.b;} throw std::runtime_error("stored type isn't boolean");} + int Variant::getInt () {if(t == Type::Int ) {f = true; return u.i;} throw std::runtime_error("stored type isn't integer");} + double Variant::getDouble() {if(t == Type::Double) {f = true; return u.d;} if(t == Type::Int) {f = true; return u.i;} throw std::runtime_error("stored type isn't double" );}//cast ints to doubles silently + std::string Variant::getString() {if(t == Type::String) {f = true; return s;} throw std::runtime_error("stored type isn't string" );} + // std::string Variant::getString() {f = true; return to_string();} + + // template <> bool Variant::get() {if(t == Type::Bool ) {f = true; return u.b;} throw std::runtime_error("stored type isn't boolean");} + // template <> int Variant::get() {if(t == Type::Int ) {f = true; return u.i;} throw std::runtime_error("stored type isn't integer");} + // template <> double Variant::get() {if(t == Type::Double) {f = true; return u.d;} throw std::runtime_error("stored type isn't double" );}//can get int as double + // template <> std::string Variant::get() {if(t == Type::String) {f = true; return s;} throw std::runtime_error("stored type isn't string" );} + + //@brief : set the stored value and update the type if needed + //@param v: value to store + template <> void Variant::set(const bool & v) {u.b = v; t = Type::Bool ; f = false;} + template <> void Variant::set(const int & v) {u.i = v; t = Type::Int ; f = false;} + template <> void Variant::set(const double & v) {u.d = v; t = Type::Double; f = false;} + template <> void Variant::set(const std::string& v) { s = v; t = Type::String; f = false;} + template void Variant::set(const T& v) { + static_assert(std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value, "T must be bool, int, double, or string"); + throw std::logic_error("Variant::set() shouldn't compile for this type"); + } + + //@brief : convert the currently held value to a string representatino + //@return: string representation + std::string Variant::to_string() const { + if(t == Type::String) return s; + std::stringstream ss; + if(t == Type::Bool ) ss << (u.b ? ".TRUE." : ".FALSE.");//convert bool to string + if(t == Type::Int ) ss << u.i ;//convert int to string + if(t == Type::Double) ss << u.d ;//convert double to string + return ss.str(); + } + + //@brief :check if the current value has been read + //@return: true if the current value was read since setting, false otherwise + bool Value::wasUsed() const { + return std::all_of(this->cbegin(), this->cend(), [](const Variant& v){return v.used();}); + } + + namespace detail { + //@brief: check if a string can be converted to the templated type + //@param str: string to check + //@param val: location to write parsed value + //@return : true if a value was successfully parsed, false otherwise + template + bool tryParse(std::string str, T& val) { + std::istringstream iss(str);//wrap string with istringstream + iss >> std::noskipws >> val;//try to parse as 'T' without skipping white space + return iss.eof() && !iss.fail();//make sure there wasn't an error and we consumed the entire string + } + } + + //@brief :convert a string to lower case + //@param s: string to convert + //@return : lowercase string + std::string NameList::ToLower(std::string s) { + std::transform(s.begin(), s.end(), s.begin(), [](char c){return std::tolower(c);}); + return s; + } + + //@brief : read a namelist file + //@param is: istream to read namelist from + //@param cm: comment characters (ignore lines if the first character after white space is in cm) + void NameList::read(std::istream& is, std::string cm) { + this->clear(); + char space, delim; + std::string line, key, tok; + size_t lineNum = 0; + std::getline(is, line);//for now just skip the first line + fileLines.push_back(line); + if(line.empty() ? false : std::string::npos != line.find('=')) throw std::runtime_error("namelist files cannot have key value pairs in the first line"); + ++lineNum; + bool gotComma = true;//was there a comma on the previous line + while(std::getline(is, line)) {//loop over lines while the input stream is good + //handle empty and comment lines + ++lineNum; + fileLines.push_back(line); + if(line.empty()) continue;//skip empty lines + if(std::string::npos != cm.find_first_of(line.front())) continue;//skip comment lines + if(0 == line.compare(" /")) continue;//final line + + //wrap line as input string stream + std::istringstream iss(line); + + //make sure there was a comma before this token (or it was the first token) and check if there is one afterwards + if(!gotComma) throw std::runtime_error("missing comma between previous entry and namelist line " + std::to_string(lineNum) + " \"" + line + "\""); + gotComma = false;//clear flag in case we reverse all the way through the line + iss.seekg(-1,std::ios::end);//move to end of line + const size_t len = iss.tellg();//get length + for(size_t i = 0; i <= len; i++) {//reverse loop over iss + if(std::isspace(iss.peek())) {//keep reversing through whitespace + iss.unget(); + } else {//this is an actual characater + gotComma = ',' == iss.peek();//check if the last non-white space character is a comma + iss.seekg(0);//return to begining + iss.clear();//clear eof bit + break;//we're done + } + } + + //extract " key = " and make sure it isn't a duplicate + if(!(iss >> std::noskipws >> space >> key >> std::skipws >> delim >> std::noskipws)) throw std::runtime_error("error parsing line '" + line + "' from name list"); + if(space != ' ') throw std::runtime_error("missing leading space in namelist line " + std::to_string(lineNum) + " \"" + line + "\""); + key = ToLower(key); + if(this->find(key) != this->end()) throw std::runtime_error("key \"" + key + "\" was defined twice in the name list"); + if(delim != '=') throw std::runtime_error("bad delimeter (expected '=') in namelist line " + std::to_string(lineNum) + " \"" + line + "\""); + while(std::isspace(iss.peek()) && iss) iss.get();//skip all whitespce after '=' before value + + //now extract comma delimited tokens from the reaminging input stream + //in the future (c++14/17) this probably be updated with std::quoted + Value val; + if('\'' == iss.peek()) {//we have strings to extract + iss.get();//skip ' + while(iss.good()) { + //traverse looking for next unescaped ' + char c; + tok.clear();//create an empty string to accumlate into + bool found = false; + while(iss >> c) {//extract one character from the input stream + if('\'' == c) {//we may have found a closing (unescaped quote) + if(tok.empty() ? true : '\\' != tok.back()) { + found = true;//closing found + break;//we're done + } else { + tok.pop_back();//remove escapement + } + } + tok += c;//accumulate characters into token + } + + //make sure we found the end of the string and save + if(!found) throw std::runtime_error("no closing quote for a token in line " + std::to_string(lineNum) + " \"" + line + "\""); + val.push_back(tok);//save token + + //advance to the start of the next string + if(iss >> std::skipws >> delim) { + if(',' != delim) throw std::runtime_error("unexpected delimiter between strings in line " + std::to_string(lineNum) + " \"" + line + "\""); + if(iss >> std::skipws >> delim) { + if('\'' != delim) throw std::runtime_error("unexpected delimiter string opening in line " + std::to_string(lineNum) + " \"" + line + "\""); + } + } + } + } else {//we have string representations of booleans, integers, or floating point numbers to extract + while(std::getline(iss, tok, ',')) {//tokenize + //clean up token string + tok.erase(std::remove_if(tok.begin(), tok.end(), [](const char& c){return std::isspace(c);}), tok.end());//trim white space + std::transform(tok.begin(), tok.end(), tok.begin(), [](char c){return std::tolower(c);});//convert to lower case + + //attempt to parse + if(!tok.empty()) { + int vI; + double vD; + if(0 == tok.compare(".true.")) {//first check fortran style logical + val.push_back(true); + } else if(0 == tok.compare(".false.")) { + val.push_back(false); + } else if(detail::tryParse(tok, vD)) {//if it isn't a logical it must be a number + if(detail::tryParse(tok, vI)) {//strings parse-able as int are a subset of string parse-able as fp + val.push_back(vI); + } else { + val.push_back(vD); + } + } else { + throw std::runtime_error("couldn't parse token \"" + tok + "\" from line " + std::to_string(lineNum) + " \"" + line + "\" as bool, int, or float (strings must be in single quotes, e.g. key = 'value')"); + } + } + } + + //check for a mix of parsed types + bool hasBool = false, hasInt = false, hasDouble = false; + for(const Variant& v : val) { + switch(v.getType()) { + case Type::Bool : hasBool = true; break; + case Type::Int : hasInt = true; break; + case Type::Double: hasDouble = true; break; + case Type::None : //intentional fall through + case Type::String: throw std::logic_error("parsed bool/int/double to string or none"); + } + } + + //check for mixed types + if((hasInt || hasDouble) && hasBool) throw std::runtime_error("line " + std::to_string(lineNum) + " \"" + line + "\" has a mix of numbers and booleans"); + + //int + double ==> double + if(hasInt && hasDouble) {//!hasBool + for(Variant& v : val) { + if(Type::Int == v.getType()) {//cast this int to a double for consistency + v.set(v.getInt()); + } + } + } + } + + //save key/value pair + // if(1 != val.size()) throw std::runtime_error("only scalar inputs are currently supported"); + this->operator[](key) = val; + } + } + + //@brief : read a namelist file + //@param nm: filename of namelist to read + //@param cm: comment characters (ignore lines if the first character after white space is in cm) + void NameList::read(std::string nm, std::string cm) { + std::ifstream is(nm); + read(is, cm); + } + + //@brief : find the first token with a given key + //@param nm : key to search for + //@return : token with specified key (throws if not found) + //@note : write acces isn't public facing + Value & NameList::at(const std::string nm) { + try { + return std::map::at(ToLower(nm)); + } catch (std::out_of_range&) { + throw std::runtime_error("couldn't find `" + nm + "' in namelist"); + } + } + + //@brief : find the first token with a given key + //@param nm : key to search for + //@return : token with specified key (throws if not found) + Value const& NameList::at(const std::string nm) const { + try { + return std::map::at(ToLower(nm)); + } catch (std::out_of_range&) { + throw std::runtime_error("couldn't find `" + nm + "' in namelist"); + } + } + + //@brief : attempt to parse a list fo bool from a list of namelist parameters + //@param nm : key to search for + //@return : parsed bools (throws if not found) + std::vector NameList::getBools (std::string nm) { + std::vector ret; + Value& key = at(nm); + for(size_t i = 0; i < key.size(); i++) ret.push_back(key[i].getBool ()); + return ret; + + } + + //@brief : attempt to parse a list fo int from a list of namelist parameters + //@param nm : key to search for + //@return : parsed ints (throws if not found) + std::vector NameList::getInts (std::string nm) { + std::vector ret; + Value& key = at(nm); + for(size_t i = 0; i < key.size(); i++) ret.push_back(key[i].getInt ()); + return ret; + + } + + //@brief : attempt to parse a list fo double from a list of namelist parameters + //@param nm : key to search for + //@return : parsed doubles (throws if not found) + std::vector NameList::getDoubles(std::string nm) { + std::vector ret; + Value& key = at(nm); + for(size_t i = 0; i < key.size(); i++) ret.push_back(key[i].getDouble()); + return ret; + + } + + //@brief : attempt to parse a list fo string from a list of namelist parameters + //@param nm : key to search for + //@return : parsed strings (throws if not found) + std::vector NameList::getStrings(std::string nm) { + std::vector ret; + Value& key = at(nm); + for(size_t i = 0; i < key.size(); i++) ret.push_back(key[i].getString()); + return ret; + + } + + //@brief : check if there are unused tokens + //@return: false if all tokens have been parse (via get___), false otherwise + bool NameList::fullyParsed() const { + return std::all_of(this->cbegin(), this->cend(), [](const std::pair& p){return p.second.wasUsed();}); + } + + //@brief : get a list of unused tokens + //@return: names of all unused tokens + std::string NameList::unusedTokens() const { + std::string unused; + for(std::map::const_iterator iter = this->cbegin(); iter != this->cend(); ++iter) {//loop over key/value pairs + if(!iter->second.wasUsed()) {//check if this value was used + if(!unused.empty()) unused += ',';//seperate tokens + unused += iter->first;//add token key + } + } + return unused; + } + +#ifdef NML_USE_H5 + //@brief : write all parsed tokens to an hdf file + //@param grp: hgf group to write values to + void NameList::writeParameters(H5::Group grp) { + //loop over key/value pairs writing values of anything that was actually parsed + for(std::map::const_iterator iter = this->cbegin(); iter != this->cend(); ++iter) { + if(iter->second.wasUsed()) {//check if this value was used + switch(iter->second.getType()) { + case Type::None : throw std::logic_error("cannot write None typed variant to HDF file"); + + case Type::Bool : { + std::vector vBool = getBools (iter->first); + std::vector vals(vBool.begin(), vBool.end()); + hsize_t dims[1] = {vals.size()}; + grp.createDataSet(iter->first, H5::PredType::NATIVE_HBOOL , H5::DataSpace(1, dims)).write(vals.data(), H5::PredType::NATIVE_HBOOL ); + } break; + + case Type::Int : { + std::vector vals = getInts (iter->first); + hsize_t dims[1] = {vals.size()}; + grp.createDataSet(iter->first, H5::PredType::NATIVE_INT , H5::DataSpace(1, dims)).write(vals.data(), H5::PredType::NATIVE_INT ); + } break; + + case Type::Double: { + std::vector vals = getDoubles(iter->first); + hsize_t dims[1] = {vals.size()}; + grp.createDataSet(iter->first, H5::PredType::NATIVE_DOUBLE , H5::DataSpace(1, dims)).write(vals.data(), H5::PredType::NATIVE_DOUBLE ); + } break; + + case Type::String: { + std::vector vStr = getStrings(iter->first); + std::vector vals; + for(const std::string& s : vStr) vals.push_back(s.data()); + hsize_t dims[1] = {vals.size()}; + grp.createDataSet(iter->first, H5::StrType(0, H5T_VARIABLE), H5::DataSpace(1, dims)).write(vals.data(), H5::StrType(0, H5T_VARIABLE)); + } break; + } + } + } + + //write an attribute with any unused values + std::string unused = unusedTokens(); + if(!unused.empty()) grp.createAttribute("Unused Tokens", H5::StrType(0, H5T_VARIABLE), H5::DataSpace(H5S_SCALAR)).write(H5::StrType(0, H5T_VARIABLE), unused); + } + + //@brief : write the raw file to a dataset + //@param grp: hgf group to create dataset in + //@param nm : name of dataset to write + void NameList::writeFile(H5::Group grp, std::string nm) const { + std::vector vals; + for(const std::string& s : fileLines) vals.push_back(s.data()); + hsize_t dims[1] = {vals.size()}; + grp.createDataSet(nm, H5::StrType(0, H5T_VARIABLE), H5::DataSpace(1, dims)).write(vals.data(), H5::StrType(0, H5T_VARIABLE)); + } + +#endif + +} + +#endif//_NML_H_ diff --git a/include/util/svg.hpp b/include/util/svg.hpp new file mode 100644 index 0000000..c870f40 --- /dev/null +++ b/include/util/svg.hpp @@ -0,0 +1,1351 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _SVG_HPP_ +#define _SVG_HPP_ + +#include +#include + +namespace svg { + struct Element;//an slement of an svg + struct Group ;//element groups + + class SVG { + public: + //@brief : create an SVG + //@param w: width of SVG + //@param h: height of SVG + SVG(const double w, const double h); + + //@brief : copy constructor + //@param s: svg to copy + SVG(const SVG& s); + //@brief : add an element to the svg + //@param el: element to add + void add(const Element& el); + + //@brief : add a group to the svg + //@param gr: element to add + void add(const Group& gr); + + //@brief : write the SVG to an ostream + //@param fileName: name of file to write to + void write(std::string fileName) const; + + //@brief : write svg to an ostream + //@brief os : ostream to write to + //@brief svg: svg to write + friend std::ostream& operator<<(std::ostream& os, const SVG& svg) {return os << svg.ss.str() << "\n";} + + private: + double width ;//canvas width in pixels + double height ;//canvas height in pixels + double view[4];//viewbox as minx, miny, width, height + std::stringstream ss ;//svg (without closing) + }; + + + //////////////////////////////////////////////////////////////////////// + // Elements, Groups, and Transformations // + //////////////////////////////////////////////////////////////////////// + + struct Transform;//spatial transformations + + //an SVG is a collection of elements + struct Element { + //@brief : convert the element to an XML string + //@param os: ostream to write to + virtual std::ostream& write(std::ostream& os) const = 0; + + //@brief : write the element to an ostream + //@brief os: ostream to write to + //@brief el: element to write + friend std::ostream& operator<<(std::ostream& os, const Element& el) {return el.write(os);} + + //@brief : get the string representation of the element + //@return: XML string + std::string to_string() const; + + //@brief : transform the element + //@param trs: transformation to apply + //@return : transformed element (in single component group) + Group transform(const Transform trs) const; + + //@brief : add an additional translation + //@param x: x translation + //@param y: y translation + //@return : this + Group translate(const double x, const double y) const; + + //@brief : add an additional scaling + //@param factor: scale factor (1 = no scaling) + //@return : this + Group scale(const double factor) const; + + //@brief : add an additional rotation + //@param angle: rotation angle in degrees + //@return : this + //@note : positive angles are clockwise rotations + Group rotate(const double angle) const; + + //@brief : create a group with a second element + //@param el: other element to add + //@return : group of {this, el} + Group add(const Element& el) const; + + //the element class needs to be abstract + virtual ~Element() = 0; + }; + + //a group of elements (potentially with a transformation applied) + struct Group { + //@brief : add an element to the group + //@param el: element to add + //@return : this + Group& add(const Element& el); + + //@brief : add an additional transformation + //@param trs: transform to add + //@return : this + Group& transform(const Transform trs); + + //@brief : add an additional translation + //@param x: x translation + //@param y: y translation + //@return : this + Group& translate(const double x, const double y); + + //@brief : add an additional scaling + //@param factor: scale factor (1 = no scaling) + //@return : this + Group& scale(const double factor); + + //@brief : add an additional rotation + //@param angle: rotation angle in degrees + //@return : this + //@note : positive angles are clockwise rotations + Group& rotate(const double angle); + + //@brief : convert the group to an XML string + //@param os: ostream to write to + std::ostream& write(std::ostream& os, std::string prefix = "\t") const; + + std::vector elements ;//string representation of elements in group + std::vector transforms;//list of transformations to apply + }; + + //a spatial transformation + //currently only translation, isotropic scaling, and rotation are supported + struct Transform { + //@brief : set the rotation angle in degrees + //@param x: x translation + //@param y: y translation + //@return : this + Transform& translate(const double x, const double y); + + //@brief : set the rotation angle in degrees + //@param factor: scale factor (1 = no scaling) + //@return : this + Transform& scale(const double factor); + + //@brief : set the rotation angle in degrees + //@param angle: rotation angle in degrees + //@return : this + //@note : positive angles are clockwise rotations + Transform& rotate(const double angle); + + //@brief : construct a transformation from a translation + //@param x: x translation + //@param y: y translation + //@return : translation transform + static Transform Translate(const double x, const double y); + + //@brief : construct a transformation from a translation + //@param factor: scale factor (1 = no scaling) + //@return : translation transform + static Transform Scale(const double factor); + + //@brief : construct a transformation from a translation + //@param angle: rotation angle in degrees + //@return : translation transform + //@note : positive angles are clockwise rotations + static Transform Rotate(const double angle); + + //@brief : convert the element to an XML string + //@param os: ostream to write to + std::ostream& write(std::ostream& os) const; + + //@brief : write the transform to an ostream + //@brief os : ostream to write to + //@brief trs: transform to write + friend std::ostream& operator<<(std::ostream& os, const Transform& trs) {return trs.write(os);} + + private: + double tx ;//x translation + double ty ;//y translation + double scl;//scale + double rot;//rotation in degrees + uint8_t flg;//transform flags + }; + + //////////////////////////////////////////////////////////////////////// + // Element Attributes // + //////////////////////////////////////////////////////////////////////// + + //@brief: class to represent an svg color + struct Color { + bool none ;//flag for the special 'none' color + double rgb[3];//rgb values as 0->1 + + //@brief: construct an empty color ('none') + Color() : none(true) {} + + //@brief : construct a color from an rgb triplet + //@param r: fractional red value [0,1] + //@param g: fractional green value [0,1] + //@param b: fractional blue value [0,1] + Color(const double r, const double g, const double b); + + //@brief : set the color to no color + //@return: *this + Color& setNone(); + + //@brief : set the color + //@param r: fractional red value [0,1] + //@param g: fractional green value [0,1] + //@param b: fractional blue value [0,1] + //@return : *this + Color& setRGB(const double r, const double g, const double b); + + //@brief : write color to an ostream + //@param os: ostream to write to + std::ostream& write(std::ostream& os) const; + + //@brief : write color to an ostream + //@brief os : ostream to write to + //@brief clr: color to write + friend std::ostream& operator<<(std::ostream& os, const Color& clr) {return clr.write(os);} + }; + + //@brief: class to represent an svg stroke + struct Stroke { + double width;//width of stroke + Color color;//color of stroke + + //@brief : construct a stroke from a width + color + //@param w: width of stroke + //@param c: color of stroke + //@note : defaults to 1 wide black + Stroke(const double w = 1, const Color c = Color(0, 0, 0)) : color(c), width(w) {} + + //@brief : construct a stroke from a color only + //@param c: color of stroke + Stroke(const Color c) : Stroke(1, c) {} + + //@brief : set stroke width + //@param w: width to set + //@return : this + Stroke& setWidth(const double w); + + //@brief : set stroke color + //@param c: color to set + //@return : this + Stroke& setColor(const Color c); + + //@brief : set the color + //@param r: fractional red value [0,1] + //@param g: fractional green value [0,1] + //@param b: fractional blue value [0,1] + //@return : *this + Stroke& setColor(const double r, const double g, const double b) {return setColor(Color(r, g, b));} + + //@brief : write stroke to an ostream + //@param os: ostream to write to + std::ostream& write(std::ostream& os) const {return os << "stroke=\"" << color << "\" stroke-width=\"" << width << "\" ";} + + //@brief : write stroke to an ostream + //@param os : ostream to write to + //@param stk: stroke to write + friend std::ostream& operator<<(std::ostream& os, const Stroke& stk) {return stk.write(os);} + }; + + //@brief: class to represent an svg fill + struct Fill { + Color color;//for now a fill is just a color (but it could be e.g. a gradient in the future) + + //@brief: default fill (none) + Fill() {} + + //@brief : construct fill from a color + //@param c: color + Fill(const Color c) : color(c) {} + + //@brief : set fill color + //@param c: color to set + Fill& setColor(const Color c); + + //@brief : set fill color + //@param c: color to set + Fill& setColor(const double r, const double g, const double b) {return setColor(Color(r, g, b));} + + //@brief : set fill color + //@param c: color to set + Fill& setNone() {return setColor(Color());} + + //@brief : write fill to an ostream + //@param os: ostream to write to + std::ostream& write(std::ostream& os) const {return os << "fill=\"" << color << "\" ";} + + //@brief : write fill to an ostream + //@param os: ostream to write to + //@param fl: fill to write + friend std::ostream& operator<<(std::ostream& os, const Fill& fl) {return fl.write(os);} + }; + + //////////////////////////////////////////////////////////////////////// + // Images // + //////////////////////////////////////////////////////////////////////// + struct Image : public Element { + enum PixelType {Gray = 1, GrayAlpha = 2, RGB = 3, RGBA = 4};//allowed pixel types + + double x ;//x coordinate of origin + double y ;//y coordinate of origin + size_t width ;//width in pixels + size_t height;//height in pixels + PixelType type ;//pixel type + std::vector buff ;//raw data buffer + + //@brief : construct an image + //@param w: width of image in pixels + //@param h: height of image in pixels + //@param p: pixel type + Image(size_t w, size_t h, PixelType p = Gray) : x(0), y(0), width(w), height(h), type(p), buff(w * h * (size_t)p, 0x00) {} + + //@brief : write complete image tag to an ostream + //@param os: ostream to write to + std::ostream& write(std::ostream& os) const; + }; + + //////////////////////////////////////////////////////////////////////// + // Basic Shapes // + //////////////////////////////////////////////////////////////////////// + + struct Shape : public Element { + Fill fill ; + Stroke stroke; + + //@brief : write complete shape tag to an ostream + //@param os: ostream to write to + std::ostream& write(std::ostream& os) const; + + //@brief : write shape tag contents to an ostream + //@param os: ostream to write to + virtual std::ostream& writeElement(std::ostream& os) const = 0; + + //@brief : set the shape's fill + //@param f: fill to set + //@return : this + Shape& setFill(const Fill& f); + + //@brief : set the shape's fill + //@param r: red [0, 1] + //@param g: red [0, 1] + //@param b: red [0, 1] + //@return : this + Shape& setFill(const double r, const double g, const double b) {return setFill(Color(r, g, b));} + + //@brief : remove the shape's fill + //@return : this + Shape& removeFill() {return setFill(Fill(Color()));} + + //@brief : set the shape's stroke + //@param s: stroke to set + //@return : this + Shape& setStroke(const Stroke& s); + + //@brief : set the shape's stroke + //@param w: width of stroke + //@param c: color of stroke + //@return : this + Shape& setStroke(const double w, const Color c) {return setStroke(Stroke(w, c));} + + //@brief : set the shape's stroke width + //@param w: width of stroke + //@return : this + Shape& setStrokeWidth(const double w) {return setStroke(w, stroke.color);} + + //@brief : set the shape's stroke color + //@param c: color of stroke + //@return : this + Shape& setStrokeColor(const Color c) {return setStroke(stroke.width, c);} + + //@brief : set the shape's stroke color + //@param c: color of stroke + //@return : this + Shape& setStrokeColor(const double r, const double g, const double b) {return setStroke(stroke.width, Color(r, g, b));} + + //@brief : remove the stroke + //@return : this + Shape& removeStroke() {return setStrokeColor(Color());} + }; + + struct Path : public Shape { + //@brief: construct an empty path + Path() {} + + //@preif : construct a path starting at specified coordinates + //@param x: x coordinate of start + //@param y: y coordinate of start + Path(const double x, const double y) {moveTo(x, y, true);} + + //@brief : start a new sub path at the given coordinates (no stroke in between) + //@param x : x coordinate of new sub path start + //@param y : y coordinate of new sub path start + //@param abs: true/false if x/y are absolute/relative (to previous position) + //@param abs: true/false if x/y are absolute/relative (to previous position) + //@note : 'M' / 'm' + void moveTo(const double x, const double y, const bool abs); + + //@brief: close the current subpath + //@note : 'Z' / 'z' + void close(); + + //@brief : draw a straight line from the current position to the given position + //@param x : x coordinate to draw line to + //@param y : y coordinate to draw line to + //@param abs: true/false if x/y are absolute/relative (to previous position) + //@note : 'L' / 'l' + void lineTo(const double x, const double y, const bool abs); + + //@brief : draw a horizontal line from the current position to the given position + //@param x : x coordinate to draw line to + //@param abs: true/false if x/y are absolute/relative (to previous position) + //@note : 'H' / 'h' + void hLineTo(const double x, const bool abs); + + //@brief : draw a vertical line from the current position to the given position + //@param y : y coordinate to draw line to + //@param abs: true/false if x/y are absolute/relative (to previous position) + //@note : 'V' / 'v' + void vLineTo(const double y, const bool abs); + + //@brief : draw a cubic bezier curve + //@param x1 : x coordinate of first control point (for start point) + //@param y1 : y coordinate of first control point (for start point) + //@param x2 : x coordinate of second control point (for end point) + //@param y2 : y coordinate of second control point (for end point) + //@param x : x coordinate of end point + //@param y : y coordinate of end point + //@param abs: true/false if x/y are absolute/relative (to previous position) + //@note : 'C' / 'c' + void curveTo(const double x1, const double y1, const double x2, const double y2, const double x, const double y, const bool abs); + + //@brief : draw a cubic bezier curve + //@param x2 : x coordinate of second control point (for end point) + //@param y2 : y coordinate of second control point (for end point) + //@param x : x coordinate of end point + //@param y : y coordinate of end point + //@param abs: true/false if x/y are absolute/relative (to previous position) + //@note : x1/y1 are reflection of the second control point on the previous command relative to the current control point + //@note : 'S' / 's' + void smoothCurveTo(const double x2, const double y2, const double x, const double y, const bool abs); + + //@brief : draw a quadratic bezier curve + //@param x1 : x coordinate of control point + //@param y1 : y coordinate of control point + //@param x : x coordinate of end point + //@param y : y coordinate of end point + //@param abs: true/false if x/y are absolute/relative (to previous position) + //@note : 'Q' / 'q' + void quadTo(const double x1, const double y1, const double x, const double y, const bool abs); + + //@brief : draw a quadratic bezier curve + //@param x : x coordinate of end point + //@param y : y coordinate of end point + //@param abs: true/false if x/y are absolute/relative (to previous position) + //@note : x1/y1 are reflection of the second control point on the previous command relative to the current control point + //@note : 'T' / 't' + void smoothQuadTo(const double x, const double y, const bool abs); + + //@brief : draw an elliptical arc + //@param rx : x axis of ellipse + //@param ry : y axis of ellipse + //@param rot: rotation of axis in degrees (relative to current coordinate system) + //@param lrg: large arc flag + //@param swp: sweep flag + //@param x : x coordinate of end point + //@param y : y coordinate of end point + //@param abs: true/false if x/y are absolute/relative (to previous position) + //@note : 'A' / 'a' + void ellipticalArc(const double rx, const double ry, const double rot, const int lrg, const int swp, const double x, const double y, const bool abs); + + //@brief : write path to an ostream + //@param os: ostream to write to + std::ostream& writeElement(std::ostream& os) const; + + private: + struct ControlPoint { + char c; + double params[6]; + }; + + std::vector pts; + }; + + //rectangle + struct Rect : public Shape { + double x ;//x origin + double y ;//y origin + double width ;//width of rectangle + double height;//height of rectangle + double rx ;//x radius of ellipse for corner round of + double ry ;//y radius of ellipse for corner round of + + //@brief : construct a rectangle + //@param x: x origin + //@param y: y origin + //@param w: width + //@param h: height + Rect(const double x, const double y, const double w, const double h) : x(x), y(y), width(w), height(h), rx(0), ry(0) {} + + //@brief : write rect to an ostream + //@param os: ostream to write to + std::ostream& writeElement(std::ostream& os) const; + }; + + //circle + struct Circle : public Shape { + double cx;//x center + double cy;//y center + double r ;//radius + + //@brief : construct a circle + //@param r : radius + //@param cx: x center + //@param cy: y center + Circle(const double r, const double cx = 0, const double cy = 0) : cx(cx), cy(cy), r(r) {} + + //@brief : write circle to an ostream + //@param os: ostream to write to + std::ostream& writeElement(std::ostream& os) const; + }; + + //ellipse + struct Ellipse : public Shape { + double cx;//x center + double cy;//y center + double rx;//x radius + double ry;//y radius + + //@brief : construct an ellipse + //@param rx: x radius + //@param ry: y radius + //@param cx: x center + //@param cy: y center + Ellipse(const double rx, const double ry, const double cx = 0, const double cy = 0) : cx(cx), cy(cy), rx(rx), ry(ry) {} + + //@brief : write ellipse to an ostream + //@param os: ostream to write to + std::ostream& writeElement(std::ostream& os) const; + }; + + //line + struct Line : public Shape { + double x1;//x start coordinate + double y1;//y start coordinate + double x2;//x end coordinate + double y2;//y end coordinate + + //@brief : construct a line + //@param x1: x start coordinate + //@param y1: y start coordinate + //@param x2: x end coordinate + //@param y2: y end coordinate + Line(const double x1, const double y1, const double x2, const double y2) : x1(x1), y1(y1), x2(x2), y2(y2) {} + + //@brief : write line to an ostream + //@param os: ostream to write to + std::ostream& writeElement(std::ostream& os) const; + }; + + //polyline + struct Polyline : public Shape { + std::vector< std::pair > pts;//xy coordinates of points + + //@brief : add a new point to the line + //@param x: x coordinate of point to add + //@param y: y coordinate of point to add + void add(const double x, const double y) {pts.emplace_back(x, y);} + + //@brief : write polyline to an ostream + //@param os: ostream to write to + std::ostream& writeElement(std::ostream& os) const; + + //@brief: get the name of this object + virtual std::string name() const {return "polyline";} + }; + + //polygon (just a closed polyline) + struct Polygon : public Polyline { + //@brief: get the name of this object + std::string name() const {return "polygon";} + + //@brief : construct a regular polygon + //@param n: number of sides (must be at least 3) + //@param r: distance from origin to points + //@param x: x origin + //@param y: y origin + static Polygon Regular(const size_t n, const double r, const double x = 0, const double y = 0); + }; + + + //////////////////////////////////////////////////////////////////////// + // Text // + //////////////////////////////////////////////////////////////////////// + + struct Text : public Element { + enum class Style {Normal, Italic, Oblique}; + enum class Weight {Normal, Bold, Bolder, Lighter}; + enum class Decoration {None = 0, Underline = 1, Overline = 2, Throughline = 4, Blink = 8};//bitmask + + double x ;//x coordinate of origin + double y ;//y coordinate of origin + std::string font ;//font family + double size ;//font size + Style style ;//font style + Weight weight;//font weight + Decoration decor ;//font decoration + std::string text ;//string + + //@brief : construct text + Text() : x(0), y(0), font("Helvetica"), size(12), style(Style::Normal), weight(Weight::Normal), decor(Decoration::None), text() {} + + //@brief : write text tag to an ostream + //@param os: ostream to write to + std::ostream& write(std::ostream& os) const; + }; +} + +//////////////////////////////////////////////////////////////////////// +// Implementation Details // +//////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include "util/base64.hpp" + +#define MINIZ_NO_STDIO +#define MINIZ_NO_TIME +#define MINIZ_NO_ZLIB_APIS +#include "miniz/miniz.c" + +#include "constants.hpp" + +namespace svg { + + //////////////////////////////////////////////////////////////////////// + // Element Members // + //////////////////////////////////////////////////////////////////////// + + //@brief : get the string representation of the element + //@return: XML string + std::string Element::to_string() const { + std::stringstream ss; + write(ss); + return ss.str(); + } + + //@brief : transform the element + //@param trs: transformation to apply + //@return : transformed element (in single component group) + Group Element::transform(const Transform trs) const { + Group grp; + return grp.add(*this).transform(trs); + } + + //@brief : add an additional translation + //@param x: x translation + //@param y: y translation + //@return : this + Group Element::translate(const double x, const double y) const { + return transform(Transform::Translate(x, y)); + } + + //@brief : add an additional scaling + //@param factor: scale factor (1 = no scaling) + //@return : this + Group Element::scale(const double factor) const { + return transform(Transform::Scale(factor)); + } + + //@brief : add an additional rotation + //@param angle: rotation angle in degrees + //@return : this + //@note : positive angles are clockwise rotations + Group Element::rotate(const double angle) const { + return transform(Transform::Rotate(angle)); + } + + //@brief : create a group with a second element + //@param el: other element to add + //@return : group of {this, el} + Group Element::add(const Element& el) const { + Group grp; + return grp.add(*this).add(el); + } + + Element::~Element() {} + + //////////////////////////////////////////////////////////////////////// + // Group Members // + //////////////////////////////////////////////////////////////////////// + + //@brief : add an element to the group + //@param el: element to add + //@return : this + Group& Group::add(const Element& el) { + elements.push_back(el.to_string()); + return *this; + } + + //@brief : add an additional transformation + //@param trs: transform to add + //@return : this + Group& Group::transform(const Transform trs) { + transforms.push_back(trs); + return *this; + } + + //@brief : add an additional translation + //@param x: x translation + //@param y: y translation + //@return : this + Group& Group::translate(const double x, const double y) { + return transform(Transform::Translate(x, y)); + } + + //@brief : add an additional scaling + //@param factor: scale factor (1 = no scaling) + //@return : this + Group& Group::scale(const double factor) { + return transform(Transform::Scale(factor)); + } + + //@brief : add an additional rotation + //@param angle: rotation angle in degrees + //@return : this + //@note : positive angles are clockwise rotations + Group& Group::rotate(const double angle) { + return transform(Transform::Rotate(angle)); + } + + //@brief : convert the group to an XML string + //@param os: ostream to write to + std::ostream& Group::write(std::ostream& os, std::string prefix) const { + if(transforms.empty()) { + //open group + os << prefix << "\n"; + + //loop over elements writing + for(const std::string& el : elements) os << prefix << '\t' << el << '\n'; + + //close group + os << prefix << "\n"; + } else { + //loop over transforms opening a group + for(size_t i = 0; i < transforms.size(); i++) { + os << prefix; + for(size_t j = 0; j < i; j++) os << '\t'; + os << "\n";//open group + } + + //loop over elements writing + for(const std::string& el : elements) { + os << prefix; + for(size_t j = 1; j <= transforms.size(); j++) os << '\t'; + os << el << '\n'; + } + + //loop over transforms closing groups + for(size_t i = transforms.size() - 1; i < transforms.size(); i--) { + os << prefix; + for(size_t j = 0; j < i; j++) os << '\t'; + os << "\n";//close group + } + } + return os; + } + + //////////////////////////////////////////////////////////////////////// + // Transform Members // + //////////////////////////////////////////////////////////////////////// + + //@brief : set the rotation angle in degrees + //@param x: x translation + //@param y: y translation + //@return : this + Transform& Transform::translate(const double x, const double y) { + tx = x; + ty = y; + flg |= 0x01; + return *this; + } + + //@brief : set the rotation angle in degrees + //@param factor: scale factor (1 = no scaling) + //@return : this + Transform& Transform::scale(const double factor) { + scl = factor; + flg |= 0x02; + return *this; + } + + //@brief : set the rotation angle in degrees + //@param angle: rotation angle in degrees + //@return : this + //@note : positive angles are clockwise rotations + Transform& Transform::rotate(const double angle) { + rot = angle; + flg |= 0x04; + return *this; + } + + //@brief : construct a transformation from a translation + //@param x: x translation + //@param y: y translation + //@return : translation transform + Transform Transform::Translate(const double x, const double y) { + return Transform().translate(x, y); + } + + //@brief : construct a transformation from a translation + //@param factor: scale factor (1 = no scaling) + //@return : translation transform + Transform Transform::Scale(const double factor) { + return Transform().scale(factor); + } + + //@brief : construct a transformation from a translation + //@param angle: rotation angle in degrees + //@return : translation transform + //@note : positive angles are clockwise rotations + Transform Transform::Rotate(const double angle) { + return Transform().rotate(angle); + } + + //@brief : convert the element to an XML string + //@param os: ostream to write to + std::ostream& Transform::write(std::ostream& os) const { + if(flg > 0) {//only both if we have a transform + os << "transform=\" "; + if(flg & 0x01) os << "translate(" << tx << ',' << ty << ") "; + if(flg & 0x02) os << "scale(" << scl << ") "; + if(flg & 0x04) os << "rotate(" << rot << ") "; + os << "\" "; + } + return os; + } + + //////////////////////////////////////////////////////////////////////// + // SVG members // + //////////////////////////////////////////////////////////////////////// + + //@brief : create an SVG + //@param w: width of SVG in pixels + //@param h: height of SVG in pixels + SVG::SVG(const double w, const double h) : width(w), height(h) { + view[0] = 0; + view[1] = 0; + view[2] = w; + view[3] = h; + + ss << "\n"; + ss << "\n"; + } + + //@brief : copy constructor + //@param s: svg to copy + SVG::SVG(const SVG& s) { + width = s.width ; + height = s.height; + std::copy(s.view, s.view + 4, view); + ss << s.ss.str(); + } + + //@brief : add an element to the svg + //@param el: element to add + void SVG::add(const Element& el) { + ss << '\t' << el << '\n'; + } + + //@brief : add a group to the svg + //@param gr: element to add + void SVG::add(const Group& gr) { + gr.write(ss, "\t"); + } + + //@brief : write the SVG to an ostream + //@param fileName: name of file to write to + void SVG::write(std::string fileName) const { + std::ofstream os(fileName); + os << *this; + } + + //////////////////////////////////////////////////////////////////////// + // Element Attributes // + //////////////////////////////////////////////////////////////////////// + + //@brief : construct a color from an rgb triplet + //@param r: fractional red value [0,1] + //@param g: fractional green value [0,1] + //@param b: fractional blue value [0,1] + Color::Color(const double r, const double g, const double b) { + none = false; + rgb[0] = r; + rgb[1] = g; + rgb[2] = b; + } + + //@brief : set the color to no color + //@return: *this + Color& Color::setNone() { + none = true; + return *this; + } + + //@brief : set the color + //@param r: fractional red value [0,1] + //@param g: fractional green value [0,1] + //@param b: fractional blue value [0,1] + //@return : *this + Color& Color::setRGB(const double r, const double g, const double b) { + rgb[0] = r; + rgb[1] = g; + rgb[2] = b; + return *this; + } + + //@brief : write color to an ostream + //@param os: ostream to write to + std::ostream& Color::write(std::ostream& os) const { + if(none) { + os << "none"; + } else { + os << "rgb(" << rgb[0]*255 << ',' << rgb[1]*255 << ',' << rgb[2]*255 << ')'; + } + return os; + } + + //@brief : set stroke width + //@param w: width to set + //@return : this + Stroke& Stroke::setWidth(const double w) { + width = w; + return *this; + } + + //@brief : set stroke color + //@param c: color to set + //@return : this + Stroke& Stroke::setColor(const Color c) { + color = c; + return *this; + } + + //@brief : set fill color + //@param c: color to set + Fill& Fill::setColor(const Color c) { + color = c; + return *this; + } + + //////////////////////////////////////////////////////////////////////// + // Images // + //////////////////////////////////////////////////////////////////////// + + //@brief : write complete image tag to an ostream + //@param os: ostream to write to + std::ostream& Image::write(std::ostream& os) const { + //sanity check + const int chan = (int) type;//are there 1, 2, 3 or 4 samples per pixel + if(buff.size() != width * height * chan) throw std::logic_error("image shape doesn't match buffer size"); + + //convert to png in memory + size_t pngSize = 0; + const mz_uint compr = MZ_BEST_COMPRESSION;//compression level [0,10] + const mz_bool flip = MZ_FALSE;//flip the image? + void *pPNG_data = tdefl_write_image_to_png_file_in_memory_ex((void*)buff.data(), (int)width, (int)height, chan, &pngSize, compr, flip); + if(!pPNG_data) throw std::runtime_error("failed to create PNG image"); + + //base64 encode + os << ""; + } + + //////////////////////////////////////////////////////////////////////// + // Basic Shapes // + //////////////////////////////////////////////////////////////////////// + + //@brief : write complete shape tag to an ostream + //@param os: ostream to write to + std::ostream& Shape::write(std::ostream& os) const { + os << "<"; + writeElement(os); + return os << fill << stroke << "/>"; + } + + //@brief : set the shape's fill + //@param f: fill to set + //@return : this + Shape& Shape::setFill(const Fill& f) { + fill = f; + return *this; + } + + //@brief : set the shape's stroke + //@param s: stroke to set + //@return : this + Shape& Shape::setStroke(const Stroke& s) { + stroke = s; + return *this; + } + + //@brief : start a new sub path at the given coordinates (no stroke in between) + //@param x : x coordinate of new sub path start + //@param y : y coordinate of new sub path start + //@param abs: true/false if x/y are absolute/relative (to previous position) + //@param abs: true/false if x/y are absolute/relative (to previous position) + //@note : 'M' / 'm' + void Path::moveTo(const double x, const double y, const bool abs) { + ControlPoint p; + p.c = abs ? 'M' : 'm'; + p.params[0] = x; + p.params[1] = y; + pts.push_back(p); + } + + //@brief: close the current subpath + //@note : 'Z' / 'z' + void Path::close() { + ControlPoint p; + p.c = 'z';//case doesn't matter for z + pts.push_back(p); + } + + //@brief : draw a straight line from the current position to the given position + //@param x : x coordinate to draw line to + //@param y : y coordinate to draw line to + //@param abs: true/false if x/y are absolute/relative (to previous position) + //@note : 'L' / 'l' + void Path::lineTo(const double x, const double y, const bool abs) { + ControlPoint p; + p.c = abs ? 'L' : 'l'; + p.params[0] = x; + p.params[1] = y; + pts.push_back(p); + } + + //@brief : draw a horizontal line from the current position to the given position + //@param x : x coordinate to draw line to + //@param abs: true/false if x/y are absolute/relative (to previous position) + //@note : 'H' / 'h' + void Path::hLineTo(const double x, const bool abs) { + ControlPoint p; + p.c = abs ? 'H' : 'h'; + p.params[0] = x; + pts.push_back(p); + } + + //@brief : draw a vertical line from the current position to the given position + //@param y : y coordinate to draw line to + //@param abs: true/false if x/y are absolute/relative (to previous position) + //@note : 'V' / 'v' + void Path::vLineTo(const double y, const bool abs) { + ControlPoint p; + p.c = abs ? 'V' : 'v'; + p.params[0] = y; + pts.push_back(p); + } + + //@brief : draw a cubic bezier curve + //@param x1 : x coordinate of first control point (for start point) + //@param y1 : y coordinate of first control point (for start point) + //@param x2 : x coordinate of second control point (for end point) + //@param y2 : y coordinate of second control point (for end point) + //@param x : x coordinate of end point + //@param y : y coordinate of end point + //@param abs: true/false if x/y are absolute/relative (to previous position) + //@note : 'C' / 'c' + void Path::curveTo(const double x1, const double y1, const double x2, const double y2, const double x, const double y, const bool abs) { + ControlPoint p; + p.c = abs ? 'C' : 'c'; + p.params[0] = x1; + p.params[1] = y1; + p.params[2] = x2; + p.params[3] = y2; + p.params[4] = x ; + p.params[5] = y ; + pts.push_back(p); + } + + //@brief : draw a cubic bezier curve + //@param x2 : x coordinate of second control point (for end point) + //@param y2 : y coordinate of second control point (for end point) + //@param x : x coordinate of end point + //@param y : y coordinate of end point + //@param abs: true/false if x/y are absolute/relative (to previous position) + //@note : x1/y1 are reflection of the second control point on the previous command relative to the current control point + //@note : 'S' / 's' + void Path::smoothCurveTo(const double x2, const double y2, const double x, const double y, const bool abs) { + ControlPoint p; + p.c = abs ? 'S' : 's'; + p.params[0] = x2; + p.params[1] = y2; + p.params[2] = x ; + p.params[3] = y ; + pts.push_back(p); + } + + //@brief : draw a quadratic bezier curve + //@param x1 : x coordinate of control point + //@param y1 : y coordinate of control point + //@param x : x coordinate of end point + //@param y : y coordinate of end point + //@param abs: true/false if x/y are absolute/relative (to previous position) + //@note : 'Q' / 'q' + void Path::quadTo(const double x1, const double y1, const double x, const double y, const bool abs) { + ControlPoint p; + p.c = abs ? 'Q' : 'q'; + p.params[0] = x1; + p.params[1] = y1; + p.params[2] = x ; + p.params[3] = y ; + pts.push_back(p); + } + + //@brief : draw a quadratic bezier curve + //@param x : x coordinate of end point + //@param y : y coordinate of end point + //@param abs: true/false if x/y are absolute/relative (to previous position) + //@note : x1/y1 are reflection of the second control point on the previous command relative to the current control point + //@note : 'T' / 't' + void Path::smoothQuadTo(const double x, const double y, const bool abs) { + ControlPoint p; + p.c = abs ? 'T' : 't'; + p.params[0] = x; + p.params[1] = y; + pts.push_back(p); + } + + //@brief : draw an elliptical arc + //@param rx : x axis of ellipse + //@param ry : y axis of ellipse + //@param rot: rotation of axis in degrees (relative to current coordinate system) + //@param lrg: large arc flag + //@param swp: sweep flag + //@param x : x coordinate of end point + //@param y : y coordinate of end point + //@param abs: true/false if x/y are absolute/relative (to previous position) + //@note : 'A' / 'a' + void Path::ellipticalArc(const double rx, const double ry, const double rot, const int lrg, const int swp, const double x, const double y, const bool abs) { + if(!((lrg == 0 || lrg == 1) && (swp == 0 || swp == 1))) throw std::runtime_error("elliptical arc flags must be 0 or 1"); + ControlPoint p; + p.c = abs ? 'A' : 'a'; + p.params[0] = rx ; + p.params[1] = ry ; + p.params[2] = rot; + p.params[4] = x ; + p.params[5] = y ; + ((int*)&p.params[3])[0] = lrg * 0x01 + swp * 0x02; + pts.push_back(p); + } + + //@brief : write rect to an ostream + //@param os: ostream to write to + std::ostream& Path::writeElement(std::ostream& os) const { + os << "path d=\""; + for(const ControlPoint& p : pts) { + os << p.c; + switch(p.c) { + // 0 parameter commands + case 'Z': + case 'z': + break; + + // 1 parameter commands + case 'H': + case 'h': + case 'V': + case 'v': + os << p.params[0] << ' '; + break; + + // 2 parameter commands + case 'M': + case 'm': + case 'L': + case 'l': + case 'T': + case 't': + os << p.params[0] << ' ' << p.params[1] << ' '; + break; + + // 4 parameter commands + case 'S': + case 's': + case 'Q': + case 'q': + os << p.params[0] << ' ' << p.params[1] << ' '; + os << p.params[2] << ' ' << p.params[3] << ' '; + break; + + // 6 parameter commands + case 'C': + case 'c': + os << p.params[0] << ' ' << p.params[1] << ' ' << p.params[2] << ' ' << p.params[3] << ' ' << p.params[4] << ' ' << p.params[5] << ' '; + break; + + // special cases + case 'A': + case 'a': + os << p.params[0] << ' ' << p.params[1] << ' ' << p.params[2] << ' '; + switch(((int*)&p.params[3])[0]) { + case 0 : os << "0 0 "; break; + case 1 : os << "1 0 "; break; + case 2 : os << "0 1 "; break; + case 3 : os << "1 1 "; break; + default: throw std::logic_error("bad ellipse flag"); + } + os << p.params[4] << ' ' << p.params[5] << ' '; + break; + + default: throw std::logic_error("bad path flag"); + } + } + return os << "\" "; + } + + //@brief : write rect to an ostream + //@param os: ostream to write to + std::ostream& Rect::writeElement(std::ostream& os) const { + return os << "rect " + << "x=\"" << x << "\" y=\"" << y << "\" " + << "width=\"" << width << "\" height=\"" << height << "\" " + << "rx=\"" << rx << "\" ry=\"" << ry << "\" "; + } + + //@brief : write circle to an ostream + //@param os: ostream to write to + std::ostream& Circle::writeElement(std::ostream& os) const { + return os << "circle cx=\"" << cx << "\" cy=\"" << cy << "\" " << "r=\"" << r << "\" "; + } + + //@brief : write ellipse to an ostream + //@param os: ostream to write to + std::ostream& Ellipse::writeElement(std::ostream& os) const { + return os << "ellipse cx=\"" << cx << "\" cy=\"" << cy << "\" rx=\"" << rx << "\" ry=\"" << ry << "\" "; + } + + //@brief : write line to an ostream + //@param os: ostream to write to + std::ostream& Line::writeElement(std::ostream& os) const { + return os << "line x1=\"" << x1 << "\" y1=\"" << y1 << "\" x2=\"" << x2 << "\" y2=\"" << y2 << "\" "; + } + + //@brief : write poly to an ostream + //@param os: ostream to write to + std::ostream& Polyline::writeElement(std::ostream& os) const { + os << name() << " points=\""; + for(const std::pair pt : pts) os << pt.first << ',' << pt.second << ' '; + return os << "\" "; + } + + //@brief : construct a regular polygon + //@param n: number of sides (must be at least 3) + //@param r: distance from origin to points + //@param x: x origin + //@param y: y origin + Polygon Polygon::Regular(const size_t n, const double r, const double x, const double y) { + Polygon ply; + for(size_t i = 0; i < n; i++) { + const double theta = emsphinx::Constants::pi2 * i / n + emsphinx::Constants::pi; + ply.add(std::sin(theta) * r + x, std::cos(theta) * r + y); + } + return ply; + } + + + //////////////////////////////////////////////////////////////////////// + // Text // + //////////////////////////////////////////////////////////////////////// + + //@brief : write text tag to an ostream + //@param os: ostream to write to + std::ostream& Text::write(std::ostream& os) const { + os << "" << text << ""; + } +} + +#endif//_SVG_HPP_ diff --git a/include/util/sysnames.hpp b/include/util/sysnames.hpp new file mode 100644 index 0000000..20ed276 --- /dev/null +++ b/include/util/sysnames.hpp @@ -0,0 +1,312 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _SYSNAMES_H_ +#define _SYSNAMES_H_ + +#include + +//@brief : get the name of the logged in user +//@return: user name +std::string getUserName(); + +//@brief : get the name of the computer (often the host name) +//@return: computer name +std::string getComputerName(); + +//@brief : create a folder if needed needed +//@param dir: directory to create +//@return : full path to createed (or existing) folder with terminating '\' or '/' +std::string buildFolder(std::string dir); + +//@brief : get the path separator character +//@return: '\\' for windows, '/' for linux +char getPathSep(); + +//@brief : get the the folder to write data shared by applications and users +//@return: windows - C:\ProgramData\ (need this text to silence compiler warnings about "\ \n") +// apple - ~/Library/Application Support/ (should be '/Library/Preferences' but that isn't useful without privileges) +// linux - ~/.local/share/ (should be '/etc' but that isn't useful without privileges) +std::string getSharedDataDir(); + +//@brief : get the the folder to write data shared by applications for current user +//@return: folder to write data shared across users +//@note : windows - C:\users\{user}\AppData\Local on windows +// apple - ~/Library/Application Support/ +// linux - ~/.local/share (should be /usr/lib but that isn't useful without privileges) +std::string getUserDataDir(); + +//@brief : get the the folder to write data for current application shared by all users +//@param app: application name +//@return : getSharedDataDir()/app +std::string getAppDataDir(std::string app = "EMSphInx"); + +//@brief : get the the folder to write data for current application and current user +//@param app: application name +//@return : getUserDataDir()/app +std::string getUserAppDataDir(std::string app = "EMSphInx"); + +//@brief : get the the folder to write data for current application shared by all users +//@param app: application name +//@return : getSharedDataDir()/app +std::string getAppDataDir(std::string app) {return buildFolder(getSharedDataDir() + app);} + +//@brief : get the the folder to write data for current application and current user +//@param app: application name +//@return : getUserDataDir()/app +std::string getUserAppDataDir(std::string app) {return buildFolder(getUserDataDir() + app);} + +//@brief : check if a file exists +//@param name: file name to check existance of +//@return : true if the file exists, false otherwise +bool fileExists(std::string name); + +//@brief : determine the size of a file in bytes +//@param filename: name of file to get size of +//@return : size of file in bytes (-1 if the file doesn't exist) +std::int64_t fileSize(const char* name); +std::int64_t fileSize(std::string name) {return fileSize(name.c_str());} + +//////////////////////////////////////////////////////////////////////////////// +// Implementation Details // +//////////////////////////////////////////////////////////////////////////////// + +#include + +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) + //limit windows includes + #ifndef NOMINMAX//don't redefine + #define NOMINMAX//the min/max macros can cause issues with std::min/max + #endif + #ifndef WIN32_LEAN_AND_MEAN//don't redefine + #define WIN32_LEAN_AND_MEAN//we don't need much, don't bother including it all the extra bits + #endif + + //define windows version (for GetFileSizeEx) + #ifndef WINVER//don't redefine + #define WINVER 0x0501//this is windows xp so it shouldn't be asking for too much... + #endif + #include + #include + #include + + //@brief : get the name of the logged in user + //@return: user name + std::string getUserName() { + char uNm[UNLEN+1]; + DWORD szUser = sizeof(uNm); + if(GetUserNameA(uNm, &szUser)) return std::string(uNm);//or GetUserNameEx + throw std::runtime_error("failed to GetUserName"); + } + + //@brief : get the name of the computer (often the host name) + //@return: computer name + std::string getComputerName() { + char hNm[UNLEN+1]; + DWORD szHost = sizeof(hNm); + if(GetComputerNameA(hNm, &szHost)) return std::string(hNm); + throw std::runtime_error("failed to GetComputerName"); + } + + //@brief : create the specified directory if needed and return name + //@param dir: directory to create + //@return : full path to created (or existing) folder with terminating '\' + std::string buildFolder(const std::string dir) { + if(CreateDirectoryA(dir.c_str(), NULL) || ERROR_ALREADY_EXISTS == GetLastError()) return dir + '\\';//make sure the directory exists and return + throw std::runtime_error("failed to CreateDirectory"); + } + + //@brief : get the path separator character + //@return: '\\' for windows, '/' for linux + char getPathSep() {return '\\';} + + //@brief : get the the folder to write data shared by applications and users + //@return: windows - C:\ProgramData\ (need this text to silence compiler warnings about "\ \n") + // apple - ~/Library/Application Support/ (should be '/Library/Preferences' but that isn't useful without privileges) + // linux - ~/.local/share/ (should be '/etc' but that isn't useful without privileges) + std::string getSharedDataDir() { + char dir[MAX_PATH]; + if(SHGetFolderPathA(NULL, CSIDL_COMMON_APPDATA, NULL, 0, dir)) throw std::runtime_error("SHGetFolderPathA failed");//this is C:\ProgramData (analogous to /var) + return buildFolder(dir); + } + + //@brief : get the the folder to write data shared by applications for current user + //@return: folder to write data shared across users + //@note : windows - C:\users\{user}\AppData\Local on windows + // apple - ~/Library/Application Support/ + // linux - ~/.local/share (should be /usr/lib but that isn't useful without privileges) + std::string getUserDataDir() { + char dir[MAX_PATH]; + if(SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, dir)) throw std::runtime_error("SHGetFolderPathA failed");//this is C:\users\{user}\AppData\Local (analogus to /usr) + return buildFolder(std::string(dir)); + } + + //@brief : check if a file exists + //@param name: file name to check existance of + //@return : true if the file exists, false otherwise + bool fileExists(std::string name) { + const DWORD attrib = GetFileAttributesA(name.c_str());//get info about the file + return (attrib != INVALID_FILE_ATTRIBUTES && !(attrib & FILE_ATTRIBUTE_DIRECTORY));//check if the file exists + } + + //@brief : determine the size of a file in bytes + //@param name: name of file to get size of + //@return : size of file in bytes (-1 if the file doesn't exist) + std::int64_t fileSize(const char* name) { + //first make sure the file exists + const DWORD attrib = GetFileAttributesA(name);//get info about the file + const bool fileExists = (attrib != INVALID_FILE_ATTRIBUTES && !(attrib & FILE_ATTRIBUTE_DIRECTORY));//check if the file exists + if(!fileExists) return -1; + + //if it does exist get the size + WIN32_FILE_ATTRIBUTE_DATA fad; + if(!GetFileAttributesExA(name, GetFileExInfoStandard, &fad)) throw std::runtime_error("failed to get file attributes"); + LARGE_INTEGER size; + size.HighPart = fad.nFileSizeHigh; + size.LowPart = fad.nFileSizeLow; + return (std::int64_t) size.QuadPart; + } + +#elif __APPLE__ || __linux__ || __unix__ || defined(_POSIX_VERSION) + + #include + #include + #include + #include + #include + #include + #include + + //___64 is now deprecated on mac but should still be preferred on linux + #if __APPLE__ + #include + #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050//1050 is __MAC_10_5, the first version that stat64/mmap64 are deprecated + #define __64_FUNCS_DEPRECATED_ + #endif + #endif + #ifdef __64_FUNCS_DEPRECATED_//___64 functions are deprecated (e.g. just 'stat' is the 64 bit version and 32 bit versions don't exist) + #define STAT stat + #else//explicitly use 64 bit functions + #define STAT stat64 + #endif + + //@brief : get the name of the logged in user + //@return: user name + std::string getUserName() { + struct passwd *p = getpwuid(getuid());//try to get password info (or geteuid if you want the effective user, getpwuid_r for thread save) + if(NULL == p) throw std::runtime_error("failed to getpwuid_r"); + std::string nm(p->pw_gecos);//get full user name + return nm.empty() ? std::string(p->pw_name) : nm;//fall back to login name + } + + //@brief : get the name of the computer (often the host name) + //@return: computer name + std::string getComputerName() { + char hNm[MAXHOSTNAMELEN]; + if(gethostname(hNm, MAXHOSTNAMELEN)) throw std::runtime_error("failed to gethostname"); + return std::string(hNm); + } + + //@brief : create a folder if needed needed + //@param dir: directory to create + //@return : full path to createed (or existing) folder with terminating '\' or '/' + std::string buildFolder(std::string dir) { + struct stat sb; + if(stat(dir.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)) return dir + '/';//already exists + int ret = mkdir(dir.c_str(), S_IRWXU | S_IRWXG | S_IRWXO); + if(0 != ret) throw std::runtime_error("failed to create directory " + dir); + return dir + '/'; + } + + //@brief : get the path separator character + //@return: '\\' for windows, '/' for linux + char getPathSep() {return '/';} + + //@brief : get the the folder to write data shared by applications and users + //@return: windows - C:\ProgramData\ (need this text to silence compiler warnings about "\ \n") + // apple - ~/Library/Application Support/ (should be '/Library/Preferences' but that isn't useful without privileges) + // linux - ~/.local/share/ (should be '/etc' but that isn't useful without privileges) + std::string getSharedDataDir() { + //get the data home + char* env = getenv("XDG_DATA_HOME");//first try to get $XDG_DATA_HOME + if(NULL != env) return buildFolder(env);//got XDG_DATA_HOME + env = getenv("HOME");//fall back to $HOME/.local/share + #if __APPLE__ + const std::string par = "/Library"; + const std::string cld = "Application Support"; + #else + const std::string par = "/.local"; + const std::string cld = "share"; + #endif + if(NULL != env) return buildFolder(buildFolder(std::string(env) + par) + cld);//got HOME + + //if there we no environment variables fall back to passwd + struct passwd *p = getpwuid(getuid());//try to get password info (or geteuid if you want the effective user) + if(NULL != p) return buildFolder(buildFolder(std::string(p->pw_dir) + par) + cld);//got passwd + + //if there was no passwd we've run out of options + throw std::runtime_error("failed to getpwuid_r"); + } + + //@brief : get the the folder to write data shared by applications for current user + //@return: folder to write data shared across users + //@note : windows - C:\users\{user}\AppData\Local on windows + // apple - ~/Library/Application Support/ + // linux - ~/.local/share (should be /usr/lib but that isn't useful without privileges) + std::string getUserDataDir() { return getSharedDataDir(); } + + //@brief : check if a file exists + //@param name: file name to check existance of + //@return : true if the file exists, false otherwise + bool fileExists(std::string name) { + struct stat fileStat; + return stat(name.c_str(), &fileStat) >= 0;//try to get information about the file without opening it + } + + //@brief : determine the size of a file in bytes + //@param name: name of file to get size of + //@return : size of file in bytes (-1 if the file doesn't exist) + std::int64_t fileSize(const char* name) { + struct STAT fileStat; + const bool fileExists = STAT(name, &fileStat) >= 0;//try to get information about the file without opening it + return fileExists ? fileStat.st_size : -1; + } + +#else + + static_assert(false, "user name and host name not implemented for this os"); + +#endif + +#endif//_SYSNAMES_H_ diff --git a/include/util/threadpool.hpp b/include/util/threadpool.hpp new file mode 100644 index 0000000..32c1900 --- /dev/null +++ b/include/util/threadpool.hpp @@ -0,0 +1,235 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019, William C. Lenthe * + * All rights reserved. * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _THREADPOOL_H_ +#define _THREADPOOL_H_ + +#include +#include +#include +#include +#include +#include +#include //duration + +class ThreadPool { + private: + //helper class to hold jobs with concurrent access + struct TaskQueue { + private: + std::queue< std::function > tasks;//queue of work items (argument-less / return-less functions to be executed) + unsigned int count;//counter for number of incomplete tasks (including tasks have been popped but not run) + mutable std::condition_variable cCv ;//condition variable for queue completing + mutable std::mutex qMut ;//mutex to handle concurrent access to tasks + mutable std::mutex iMut ;//mutex for condition variable and access to count + + public: + TaskQueue() : count(0) {}//set number in queue to 0 at construction + ~TaskQueue() {clear();}//clear remaining tasks on destruction + + //@brief: add a work item to the queue + //@param task: item to add to the queue + void push(const std::function& task); + + //@brief: do some work from the queue + //@param i: index of thread doing work + //@return: true/false if the queue was/wasn't empty (work wasn't/was done) + bool process(const size_t i); + + //@brief: remove all unprocessed tasks from the queue (task that are already started won't be stopped) + void clear(); + + //@brief: check if the queue is empty + //@return: true/false if the queue is/isn't empty + //@note: only check if there are pending tasks (that haven't been started), not if there are incomplete tasks running + bool empty() const; + + //@brief: get the size of the queue + //@return: size of the queue + //@note: only counts pending tasks (that haven't been started), not incomplete tasks running + unsigned int size() const; + + //@brief: wait for the queue to be empty and all running tasks to finish + //@param timeout: duration to wait for queue to complete + //@return: true/false if the queue was/wasn't complete before the timeout + template > + bool waitComplete(std::chrono::duration timeout) const; + }; + + std::vector pool;//pool of worker threads + bool live;//flag for if threads should continue looking for work + TaskQueue tQue;//unprocessed work items + std::condition_variable wCv ;//condition variable to notify idle workers in pool + std::mutex wMut;//mutex for wCv and write access to live + + public: + //make noncopyable + ThreadPool (const ThreadPool &) = delete; + ThreadPool& operator=(const ThreadPool &) = delete; + + //@brief: determine the number of threads in a default pool + //@return: number of threads recommended by the system + static size_t Concurrency() {return std::max(1, std::thread::hardware_concurrency());} + + //@brief: construct a thread pool + //@param threadCount: number of worker threads in pool (optional) + ThreadPool(const size_t threadCount = Concurrency()); + + //@brief: clean up thread pool (waits for all tasks to finish as written) + ~ThreadPool(); + + //@brief: add a task to the work queue + //@param task: work item to add to queue + void schedule(const std::function& task); + + //@brief: get number of worker threads in pool + //@return: number of threads + size_t size() const {return pool.size();} + + //@brief: get number of items in the queue + //@return: number of items + unsigned int items() const {return tQue.size();} + + //@brief: clear out any unfinished (and unstarted) work items in the queue + void clear() {tQue.clear();} + + //@brief: blocking wait for all tasks in the queue to finish + //@param timeout: duration to wait for tasks to complete + //@return: true/false if the the tasks were / weren't completed before timeout + template > + bool waitAll(std::chrono::duration timeout) const {return tQue.waitComplete(timeout);} + + //@brief: blocking wait for all tasks in the queue to finish + void waitAll() const {while(!waitAll(std::chrono::hours(1))){};}//wait indefinitely in 1 hour increments +}; + +///////////////////////////////// +// ThreadPool member functions // +///////////////////////////////// + +#include //move + +//@brief: construct a thread pool +//@param threadCount: number of worker threads in pool (optional) +ThreadPool::ThreadPool(const size_t threadCount) : live(true) { + std::function func = [this](const size_t i)->void{//every thread will run the same function: + std::function workItem;//the thread does work of type void() + while(live) {//keep looking for work as long as the pool is live + if(tQue.process(i)) {//try to do some work and check if the queue is empty (no work is available) + std::unique_lock lock(wMut);//lock condition variable mutex + if(live && tQue.empty()) wCv.wait(lock);//wait for a work item to arrive (if one hasn't arrived meanwhile) + } + } + }; + for(unsigned int i = 0; i < threadCount; i++) pool.emplace_back(std::bind(func,i));//loop over threads constructing +} + +//@brief: clean up thread pool (waits for all tasks to finish as written) +ThreadPool::~ThreadPool() { + waitAll();//wait for workers to finish waiting and in progress tasks (could call tQue.clear() before to remove remaining tasks) + wMut.lock();//lock mutex so threads currently checking for work don't start sleeping before live is set to false + live = false;//tell the workers to stop looking for new work + wCv.notify_all();//wake up any workers waiting for a work item + wMut.unlock();//release mutex so sleeping threads can wake up + for(std::thread& t: pool) if(t.joinable()) t.join();//clean up workers +} + +//@brief: add a task to the work queue +//@param task: work item to add to queue +void ThreadPool::schedule(const std::function& task) { + std::lock_guard lock(wMut);//lock mutex so wCv isn't notified before a thread that just failed to get work goes to sleep + tQue.push(task);//add task to queue + wCv.notify_one();//notify a sleeping thread to wake up if needed +} + +///////////////////////////////// +// TaskQueue member functions // +///////////////////////////////// + +//@brief: add a work item to the queue +//@param task: item to add to the queue +void ThreadPool::TaskQueue::push(const std::function& task) { + std::unique_lock tLock(qMut, std::defer_lock);//build lock around queue mutex but don't acquire yet + std::unique_lock iLock(iMut, std::defer_lock);//build lock around count mutex but don't acquire yet + std::lock(tLock, iLock);//simultaneously lock qMut and iMut (TODO: replace with std::scoped_lock(qMut, iMut) in c++17) + tasks.push(task);//add task to queue + ++count;//increment count of items in queue +} + +//@brief: do some work from the queue +//@param i: index of thread doing work +//@return: true/false if the queue was/wasn't empty (work wasn't/was done) +bool ThreadPool::TaskQueue::process(const size_t i) { + std::function task;//potential work to be done + {//scope for lock_guard + std::lock_guard tLock(qMut);//lock queue mutex + if(tasks.empty()) {//another thread got to the last item in the queue first + return true;//return true if the queue was empty (work wasn't done) + } else {//this thread was able to get a work item + task = std::move(tasks.front());//get the work item + tasks.pop();//remove the work item from the queue + } + }//free lock on queue before starting work + task(i);//do the work item + std::lock_guard iLock(iMut);//lock counter mutex + if(0 == --count) cCv.notify_all();//wake any threads waiting for the queue to be complete if needed + return false;//return false if the queue wasn't empty (work was done) +} + +//@brief: remove all unprocessed tasks from the queue (task that are already started won't be stopped) +void ThreadPool::TaskQueue::clear() { + std::unique_lock tLock(qMut, std::defer_lock);//build lock around queue mutex but don't acquire yet + std::unique_lock iLock(iMut, std::defer_lock);//build lock around count mutex but don't acquire yet + std::lock(tLock, iLock);//simultaneously lock qMut and iMut (TODO: replace with std::scoped_lock(qMut, iMut) in c++17) + while(!tasks.empty()) { + tasks.pop();//remove next task + --count;//decrement count of items in queue + } + if(0 == count) cCv.notify_all();//wake any threads waiting for the queue to be complete if needed +} + +//@brief: check if the queue is empty +//@return: true/false if the queue is/isn't empty +//@note: only check if there are pending tasks (that haven't been started), not if there are incomplete tasks running +bool ThreadPool::TaskQueue::empty() const { + std::lock_guard tLock(qMut);//lock queue mutex + return tasks.empty();//return if there are pending tasks +} + +//@brief: get the size of the queue +//@return: size of the queue +//@note: only counts pending tasks (that haven't been started), not incomplete tasks running +unsigned int ThreadPool::TaskQueue::size() const { + std::lock_guard tLock(iMut);//lock count mutex + return count;//return if there are pending tasks +} + +//@brief: wait for the queue to be empty and all running tasks to finish +//@param timeout: duration to wait for queue to complete +//@return: true/false if the queue was/wasn't complete before the timeout +template +bool ThreadPool::TaskQueue::waitComplete(std::chrono::duration timeout) const { + std::unique_lock iLock(iMut);//lock counter mutex + if(count) return cCv.wait_for(iLock, timeout, [&](){return count == 0;});//wait for time to expire or queue to complete + return true;//queue was already complete +} + +#endif//_THREADPOOL_H_ diff --git a/include/util/timer.hpp b/include/util/timer.hpp new file mode 100644 index 0000000..de8a8e8 --- /dev/null +++ b/include/util/timer.hpp @@ -0,0 +1,93 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019, William C. Lenthe * + * All rights reserved. * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _TIMER_HPP_ +#define _TIMER_HPP_ + +#include +#include + +struct Timer { + std::chrono::high_resolution_clock::time_point tp; + Timer() : tp(std::chrono::high_resolution_clock::now()) {} + + //@brief : compute time since previous call (or construction) + //@param reset: should the reference time be reset + //@return : time in seconds + double poll(const bool reset = true) { + std::chrono::high_resolution_clock::time_point now = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed = now - tp; + if(reset) set(now); + return elapsed.count(); + } + + //@brief : set the reference time + //@param ref: reference time point + void set(const std::chrono::high_resolution_clock::time_point ref = std::chrono::high_resolution_clock::now()) {tp = ref;} + + //@brief : print a number of seconds formatted nicely as dy:hr:mn:sc + //@param s : number of seconds + //@param os: ostream to print to + static void PrintSeconds(const double s, std::ostream& os) { + //std::gmtime is creating a null pointer sometimes... + // const time_t iSec = (time_t) s;//get number of complete seconds + // std::tm* pTm = std::gmtime(&iSec);//format + uint64_t tm_sec = (uint64_t) s; + const uint64_t tm_year = tm_sec / 31536000ULL; + tm_sec -= tm_year * 31536000ULL; + const uint64_t tm_yday = tm_sec / 86400ULL; + tm_sec -= tm_yday * 86400ULL; + const uint64_t tm_hour = tm_sec / 3600ULL; + tm_sec -= tm_hour * 3600ULL; + const uint64_t tm_min = tm_sec / 60ULL; + tm_sec -= tm_min * 60ULL; + std::tm t; + t.tm_year = (int) tm_year; + t.tm_yday = (int) tm_yday; + t.tm_hour = (int) tm_hour; + t.tm_min = (int) tm_min ; + t.tm_sec = (int) tm_sec ; + std::tm* pTm = &t; + if (pTm->tm_yday > 0) { + os << pTm->tm_yday << ':'; + if(pTm->tm_hour < 10) os << '0'; + os << pTm->tm_hour << ':'; + if(pTm->tm_min < 10) os << '0'; + os << pTm->tm_min << ':'; + if(pTm->tm_sec < 10) os << '0'; + os << pTm->tm_sec; + } else if(pTm->tm_hour > 0) { + os << pTm->tm_hour << ':'; + if(pTm->tm_min < 10) os << '0'; + os << pTm->tm_min << ':'; + if(pTm->tm_sec < 10) os << '0'; + os << pTm->tm_sec; + } else if(pTm->tm_min > 0) { + os << pTm->tm_min << ':'; + if(pTm->tm_sec < 10) os << '0'; + os << pTm->tm_sec; + } else { + os << pTm->tm_sec << 's'; + } + } +}; + +#endif//_TIMER_HPP_ diff --git a/include/wx/BibtexDialog.h b/include/wx/BibtexDialog.h new file mode 100644 index 0000000..d7c4f27 --- /dev/null +++ b/include/wx/BibtexDialog.h @@ -0,0 +1,128 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _BIBTEX_DLG_H_ +#define _BIBTEX_DLG_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/////////////////////////////////////////////////////////////////////////////// +/// Class BibtexDialog +/////////////////////////////////////////////////////////////////////////////// +class BibtexDialog : public wxDialog { + private: + + protected: + wxTextCtrl* m_txt ; + wxButton * m_btnCpy; + wxCheckBox* m_chk ; + wxButton * m_ok ; + + void CopyData ( wxCommandEvent& event ) { + if (wxTheClipboard->Open()) { + wxTheClipboard->SetData( new wxTextDataObject(m_txt->GetValue()) ); + wxTheClipboard->Close(); + } + } + void DismissDialog( wxCommandEvent& event ) { Close(); } + + public: + + bool Silence() const {return m_chk->GetValue();} + + BibtexDialog( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxEmptyString, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 475,325 ), long style = wxDEFAULT_DIALOG_STYLE ); + ~BibtexDialog(); + +}; + +/////////////////////////////////////////////////////////////////////////// + +BibtexDialog::BibtexDialog( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style ) { + this->SetSizeHints( wxDefaultSize, wxDefaultSize ); + + wxBoxSizer* vSizer = new wxBoxSizer( wxVERTICAL ); + wxBoxSizer* hSizer = new wxBoxSizer( wxHORIZONTAL ); + + wxStaticText* staTxt = new wxStaticText( this, wxID_ANY, wxT("If you find this software useful, please consider citing the corresponding paper:"), wxDefaultPosition, wxDefaultSize, 0 ); staTxt->Wrap( -1 ); + + std::string key = "@article{EMSphInx,\n\ttitle = \"A spherical harmonic transform approach to the indexing of electron back-scattered diffraction patterns\",\n\tjournal = \"Ultramicroscopy\",\n\tvolume = \"207\",\n\tpages = \"112841\",\n\tyear = \"2019\",\n\tissn = \"0304-3991\",\n\tdoi = \"https://doi.org/10.1016/j.ultramic.2019.112841\",\n\tauthor = \"W.C. Lenthe and S. Singh and M. De Graef\",\n}"; + m_txt = new wxTextCtrl( this, wxID_ANY, key , wxDefaultPosition, wxDefaultSize, wxTE_DONTWRAP|wxTE_MULTILINE|wxTE_READONLY ); + m_btnCpy = new wxButton ( this, wxID_ANY, wxT("Copy to Clipboard" ), wxDefaultPosition, wxDefaultSize, 0 ); + m_chk = new wxCheckBox( this, wxID_ANY, wxT("Don't show this message again"), wxDefaultPosition, wxDefaultSize, 0 ); + m_ok = new wxButton ( this, wxID_ANY, wxT("Dismiss" ), wxDefaultPosition, wxDefaultSize, 0 ); + m_btnCpy->SetBitmap( wxArtProvider::GetBitmap( wxART_COPY, wxART_BUTTON ) ); + m_ok->SetDefault(); + + vSizer->Add( staTxt , 0, wxALL , 5 ); + vSizer->Add( m_txt , 1, wxALL|wxEXPAND , 5 ); + vSizer->Add( m_btnCpy, 0, wxALL|wxALIGN_CENTER_HORIZONTAL, 5 ); + + hSizer->AddStretchSpacer(); + hSizer->Add( m_chk , 0, wxALL|wxALIGN_CENTER_VERTICAL , 5 ); + hSizer->AddStretchSpacer(); + hSizer->Add( m_ok , 0, wxALL|wxALIGN_CENTER_VERTICAL , 5 ); + hSizer->AddStretchSpacer(); + + vSizer->Add( hSizer, 0, wxEXPAND, 5 ); + + this->SetSizer( vSizer ); + this->Layout(); + + this->Centre( wxBOTH ); + + // Connect Events + m_btnCpy->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BibtexDialog::CopyData ), NULL, this ); + m_ok ->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BibtexDialog::DismissDialog ), NULL, this ); +} + +BibtexDialog::~BibtexDialog() { + m_btnCpy->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BibtexDialog::CopyData ), NULL, this ); + m_ok ->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BibtexDialog::DismissDialog ), NULL, this ); +} + + +#endif//_BIBTEX_DLG_H_ diff --git a/include/wx/EbsdNamelistWizard.h b/include/wx/EbsdNamelistWizard.h new file mode 100644 index 0000000..a753e23 --- /dev/null +++ b/include/wx/EbsdNamelistWizard.h @@ -0,0 +1,267 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _EBSD_WIZARD_H +#define _EBSD_WIZARD_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "PatternLoadPanel.h" +#include "MasterPatternSelectPanel.h" +#include "PatternCenterPanel.h" +#include "ScanDimsPanel.h" +#include "IdxParamPanel.h" +#include "EbsdSummaryPanel.h" + +#include "ValidityWizard.h" + +#include "BibtexDialog.h" +#include "IndexingFrame.h" + +/////////////////////////////////////////////////////////////////////////// + +#include "util/sysnames.hpp" // could also use wxStandardPaths + +#include "xtal/orientation_map.hpp" +#include "modality/ebsd/pattern.hpp" +#include "modality/ebsd/nml.hpp" + +/////////////////////////////////////////////////////////////////////////////// +/// Class EbsdNamelistWizard +/////////////////////////////////////////////////////////////////////////////// +class EbsdNamelistWizard : public ValidityWizard +{ + private: + emsphinx::ebsd::Namelist nml; + + protected: + PatternLoadPanel * m_patLoadPan ; + MasterPatternSelectPanel* m_mastPatSelPan; + PatternCenterPanel * m_patCenPan ; + ScanDimsPanel * m_scnDimPan ; + IdxParamPanel * m_idxPrmPan ; + EbsdSummaryPanel * m_ebsdSumPan ; + + virtual void OnNext( wxCommandEvent& event ) { + if(5 == m_book->GetSelection()) { + wxMessageDialog dlg(this, "Namelist Complete", "", wxYES_NO|wxCANCEL|wxCENTRE|wxICON_QUESTION); + dlg.SetYesNoLabels ("Index Now", "Export Namelist"); + int res = dlg.ShowModal(); + + if(wxID_NO == res) {//save namelist file + wxFileDialog svDlg(this, _("Save Namelist file"), "", "", "Namelist files (*.nml)|*.nml", wxFD_SAVE|wxFD_OVERWRITE_PROMPT); + if(svDlg.ShowModal() == wxID_CANCEL) return;//cancel + std::ofstream os(svDlg.GetPath().ToStdString()); + os << nml.to_string(); + } else if(wxID_YES == res) { + //create indexing window + // Close(); + IndexingFrame* frm = new IndexingFrame(NULL); + frm->setNml(nml); + wxImage wim = m_scnDimPan->getMap(); + frm->setImage(wim); + frm->Show(); + // return; + } else {//don't do anything on wxID_CANCEL + return; + } + + //if we made it this far everything made it, ask users to cite papers + if(!wxFileExists(getUserAppDataDir() + "silenced")) { + BibtexDialog bDlg(this); + bDlg.ShowModal(); + if(bDlg.Silence()) std::ofstream os(getUserAppDataDir() + "silenced"); + } + } + ValidityWizard::OnNext(event); + } + + virtual void PageChanging( wxBookCtrlEvent& event ) { + switch(event.GetOldSelection()) {//what page are we leaving + case 0://pattern load + if(!m_patLoadPan->LoadImages()) event.Veto();//make sure patterns are valid before allowing page turn + nml.patFile = m_patLoadPan->getFile().ToStdString(); + nml.patName = m_patLoadPan->getAux(); + nml.patDims[0] = m_patLoadPan->getW(); + nml.patDims[1] = m_patLoadPan->getH(); + + nml.circRad = m_patLoadPan->getCirc(); + nml.gausBckg = m_patLoadPan->getBckg(); + nml.nRegions = m_patLoadPan->getNreg(); + + m_patCenPan->setBinnedPix(m_patLoadPan->getW(), m_patLoadPan->getH());//save binned detector size + break; + + case 1://master pattern + nml.masterFiles = m_mastPatSelPan->getSelected(); + break; + + case 2://pattern center + m_patCenPan->getPatternCenter(nml.pctr[0], nml.pctr[1], nml.pctr[2]); + nml.ven = "EMsoft"; + nml.thetac = m_patCenPan->getDetTlt(); + nml.delta = m_patCenPan->getDelta(); + break; + + case 3://scan dimensions + nml.scanDims[0] = m_scnDimPan->getW(); + nml.scanDims[1] = m_scnDimPan->getH(); + nml.scanSteps[0] = m_scnDimPan->getX(); + nml.scanSteps[1] = m_scnDimPan->getY(); + nml.roi = m_scnDimPan->getRoi(); + break; + + case 4://indexing paramters + nml.bw = m_idxPrmPan->getBw (); + nml.normed = m_idxPrmPan->getNorm(); + nml.refine = m_idxPrmPan->getRef (); + nml.dataFile = m_idxPrmPan->getDataFile (); + nml.vendorFile = m_idxPrmPan->getVendorFile(); + nml.ipfName = m_idxPrmPan->getIpfFile (); + nml.qualName = m_idxPrmPan->getCiFile (); + break; + + case 5://ebsd summary + break; + + } + + if(5 == event.GetSelection()) { + m_ebsdSumPan->setNamelist(&nml); + } + } + + void PatternFileChanged(wxFileDirPickerEvent& event) { + //search for associated scan file and use to populate subsequent pages + nml.patFile = m_patLoadPan->getFile().ToStdString(); + nml.patName = m_patLoadPan->getAux(); + std::shared_ptr< std::vector > ciMap = std::make_shared< std::vector >(); + std::shared_ptr< std::vector > iqMap = std::make_shared< std::vector >(); + if(nml.findScanFile(iqMap.get(), ciMap.get())) { + m_patCenPan->setPatternCenter(nml.pctr[0], nml.pctr[1], nml.pctr[2], nml.ven); + m_patCenPan->setDetTlt(nml.thetac); + m_scnDimPan->setW(nml.scanDims[0]); + m_scnDimPan->setH(nml.scanDims[1]); + m_scnDimPan->setX(nml.scanSteps[0]); + m_scnDimPan->setY(nml.scanSteps[1]); + } else { + m_patCenPan->clear();//clear any stored pattern center + } + + m_scnDimPan->setMaps(iqMap, ciMap, m_patLoadPan->getNum());//set maps (empty or not) + pattern count + m_scnDimPan->setPats( emsphinx::ebsd::PatternFile::Read(m_patLoadPan->getFile().ToStdString(), m_patLoadPan->getAux(), m_patLoadPan->getW(), m_patLoadPan->getH()) ); + } + + public: + + EbsdNamelistWizard( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("EBSD Indexing Wizard"), const wxBitmap& bitmap = wxNullBitmap, const wxPoint& pos = wxDefaultPosition, long style = wxDEFAULT_DIALOG_STYLE ); + ~EbsdNamelistWizard(); +}; + +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version Oct 26 2018) +// http://www.wxformbuilder.org/ +// +// PLEASE DO *NOT* EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// + +EbsdNamelistWizard::EbsdNamelistWizard( wxWindow* parent, wxWindowID id, const wxString& title, const wxBitmap& bitmap, const wxPoint& pos, long style ) : +ValidityWizard( parent, id, title, pos ) +{ + m_patLoadPan = new PatternLoadPanel (m_book); + m_mastPatSelPan = new MasterPatternSelectPanel(m_book); + m_patCenPan = new PatternCenterPanel (m_book); + m_scnDimPan = new ScanDimsPanel (m_book); + m_idxPrmPan = new IdxParamPanel (m_book); + m_ebsdSumPan = new EbsdSummaryPanel (m_book); + m_ebsdSumPan->EnableEditing(false);//editin shoulw be done through wizard + + AddPage(m_patLoadPan ); + AddPage(m_mastPatSelPan); + AddPage(m_patCenPan ); + AddPage(m_scnDimPan ); + AddPage(m_idxPrmPan ); + AddPage(m_ebsdSumPan ); + m_book->SetSelection(0); + this->SetClientSize(m_book->GetBestSize()); + + //read known master patterns from file + std::vector masterLib; + wxFileConfig config( wxEmptyString, wxEmptyString, getUserAppDataDir() + "MasterLib.ini"); + size_t numEtr = config.GetNumberOfEntries(); + for(size_t i = 0; i < numEtr; i++) { + std::ostringstream ss; + ss << i; + wxString str; + config.Read(ss.str(), &str); + masterLib.push_back(str); + } + m_mastPatSelPan->setLibrary(masterLib); + + m_patLoadPan->Connect( wxEVT_COMMAND_FILEPICKER_CHANGED, wxFileDirPickerEventHandler( EbsdNamelistWizard::PatternFileChanged ), NULL, this ); +} + +EbsdNamelistWizard::~EbsdNamelistWizard() { + m_patLoadPan->Disconnect( wxEVT_COMMAND_FILEPICKER_CHANGED, wxFileDirPickerEventHandler( EbsdNamelistWizard::PatternFileChanged ), NULL, this ); + + //save known master patterns to config file + std::vector masterLib = m_mastPatSelPan->getLibrary(); + wxFileConfig config( wxEmptyString, wxEmptyString, getUserAppDataDir() + "MasterLib.ini"); + config.DeleteAll(); + for(size_t i = 0; i < masterLib.size(); i++) { + std::ostringstream ss; + ss << i; + config.Write(ss.str(), masterLib[i]); + } + config.Flush(); +} + +#endif//_EBSD_WIZARD_H \ No newline at end of file diff --git a/include/wx/EbsdSummaryPanel.h b/include/wx/EbsdSummaryPanel.h new file mode 100644 index 0000000..99942c3 --- /dev/null +++ b/include/wx/EbsdSummaryPanel.h @@ -0,0 +1,350 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _EBSD_SUM_H_ +#define _EBSD_SUM_H_ + +#include +#include +#include +#include + +#include "wx/ValidityPanel.h" + +#include "modality/ebsd/nml.hpp" + +/////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +/// Class EbsdSummaryPanel +/////////////////////////////////////////////////////////////////////////////// +class EbsdSummaryPanel : public ValidityPanel { + private: + + protected: + wxPropertyGrid* m_propGrid ; + + //input data + wxPGProperty* m_propInData ;//wxPropertyCategory + wxPGProperty* m_propIpth ;//wxDirProperty + wxPGProperty* m_propPatFile ;//wxFileProperty + wxPGProperty* m_propPatDset ;//wxStringProperty + wxPGProperty* m_propMP ;//wxArrayStringProperty + wxPGProperty* m_propPsym ;//wxFileProperty + + //pattern processing + wxPGProperty* m_patProc ;//wxPropertyCategory + wxPGProperty* m_propPatSz ;//wxStringProperty + wxPGProperty* m_propPatW ;//wxUIntProperty + wxPGProperty* m_propPatH ;//wxUIntProperty + wxPGProperty* m_propCirc ;//wxBoolProperty + wxPGProperty* m_propBckg ;//wxUIntProperty + wxPGProperty* m_propNreg ;//wxUIntProperty + + //camera calibration + wxPGProperty* m_propCamCalib;//wxPropertyCategory + wxPGProperty* m_propDlt ;//wxFloatProperty + wxPGProperty* m_propVen ;//wxEnumProperty + wxPGProperty* m_propPctr ;//wxStringProperty + wxPGProperty* m_propPcenX ;//wxFloatProperty + wxPGProperty* m_propPcenY ;//wxFloatProperty + wxPGProperty* m_propPcenZ ;//wxFloatProperty + wxPGProperty* m_propThtC ;//wxFloatProperty + + //scan dimensions + wxPGProperty* m_propScanInfo;//wxPropertyCategory + wxPGProperty* m_propScnSz ;//wxStringProperty + wxPGProperty* m_propScnW ;//wxUIntProperty + wxPGProperty* m_propScnH ;//wxUIntProperty + wxPGProperty* m_propScnDx ;//wxFloatProperty + wxPGProperty* m_propScnDy ;//wxFloatProperty + wxPGProperty* m_propRoi ;//wxStringProperty + + //indexing parameters + wxPGProperty* m_propIdxPrm ;//wxPropertyCategory + wxPGProperty* m_propBw ;//wxUIntProperty + wxPGProperty* m_propNrm ;//wxBoolProperty + wxPGProperty* m_propRef ;//wxBoolProperty + wxPGProperty* m_propThd ;//wxUIntProperty + wxPGProperty* m_propBatSz ;//wxUIntProperty + + //output data + wxPGProperty* m_propOutData ;//wxPropertyCategory + wxPGProperty* m_propOpth ;//wxDirProperty + wxPGProperty* m_propDatafile;//wxFileProperty + wxPGProperty* m_propVenFile ;//wxFileProperty + wxPGProperty* m_propIpf ;//wxFileProperty + wxPGProperty* m_propQual ;//wxFileProperty + + void updatePctr() {}//TODO update values on change + + void PropChanged( wxPropertyGridEvent& event ) {if(m_propVen == event.GetProperty()) updatePctr();} + + emsphinx::ebsd::Namelist* m_nml; + + public: + + void setIdx(long bw, bool nrm, bool ref) { + m_propBw ->SetValue(WXVARIANT(bw )); + m_propNrm->SetValue(WXVARIANT(nrm)); + m_propRef->SetValue(WXVARIANT(ref)); + } + + void setScnDims(long w, long h, double x, double y) { + m_propScnW ->SetValue(WXVARIANT(w)); + m_propScnH ->SetValue(WXVARIANT(h)); + m_propScnDx->SetValue(WXVARIANT(x)); + m_propScnDy->SetValue(WXVARIANT(y)); + } + + void setRoi(emsphinx::RoiSelection& roi) { + m_propRoi->SetValue(WXVARIANT(wxString(roi.to_string()))); + } + + void setPatDims(long w, long h) { + m_propPatW->SetValue(WXVARIANT(w)); + m_propPatH->SetValue(WXVARIANT(h)); + } + + bool setPctr(double x, double y, double z, wxString ven) { + if ("EMsoft" == ven) m_propVen->SetValue(WXVARIANT(0)); + else if("Bruker" == ven) m_propVen->SetValue(WXVARIANT(1)); + else if("Edax" == ven) m_propVen->SetValue(WXVARIANT(2)); + else if("Oxford" == ven) m_propVen->SetValue(WXVARIANT(3)); + else return false; + m_propPcenX->SetValue(WXVARIANT(x)); + m_propPcenY->SetValue(WXVARIANT(y)); + m_propPcenZ->SetValue(WXVARIANT(z)); + return true; + } + + void setTltDlt(double tht, double dlt) { + m_propThtC->SetValue(WXVARIANT(tht)); + m_propDlt ->SetValue(WXVARIANT(dlt)); + } + + void setImPrc(int cir, bool bckg, int nrg) { + m_propCirc->SetValue(WXVARIANT(cir )); + m_propBckg->SetValue(WXVARIANT(bckg)); + m_propNreg->SetValue(WXVARIANT(nrg )); + } + + void setInputs(std::vector mp, wxString pf, wxString aux) { + wxArrayString arr; + for(const std::string& str : mp) arr.Add(str); + m_propMP ->SetValue(WXVARIANT(arr)); + m_propPatFile->SetValue(WXVARIANT(pf )); + m_propPatDset->SetValue(WXVARIANT(aux)); + } + + void setOutputs(wxString df, wxString vf, wxString ipf, wxString ci) { + m_propDatafile->SetValue(WXVARIANT(df )); + m_propVenFile ->SetValue(WXVARIANT(vf )); + m_propIpf ->SetValue(WXVARIANT(ipf)); + m_propQual ->SetValue(WXVARIANT(ci )); + } + + void setNamelist(emsphinx::ebsd::Namelist* nml) { + m_nml = nml; + setIdx(nml->bw, nml->normed, nml->refine); + setScnDims(nml->scanDims[0], nml->scanDims[1], nml->scanSteps[0], nml->scanSteps[1]); + setRoi(nml->roi); + setPatDims(nml->patDims[0], nml->patDims[1]); + setPctr(nml->pctr[0], nml->pctr[1], nml->pctr[2], nml->ven); + setImPrc(nml->circRad, nml->gausBckg, nml->nRegions); + setTltDlt(nml->thetac, nml->delta); + setInputs(nml->masterFiles, nml->patFile, nml->patName); + setOutputs(nml->dataFile, nml->vendorFile, nml->ipfName, nml->qualName); + } + + void updateNamelist() { + if(NULL == m_nml) return; + m_nml->bw = m_propBw ->DoGetValue().GetLong(); + m_nml->normed = m_propNrm->DoGetValue().GetBool(); + m_nml->refine = m_propRef->DoGetValue().GetBool(); + + m_nml->scanDims [0] = m_propScnW ->DoGetValue().GetLong (); + m_nml->scanDims [1] = m_propScnH ->DoGetValue().GetLong (); + m_nml->scanSteps[0] = m_propScnDx->DoGetValue().GetDouble(); + m_nml->scanSteps[1] = m_propScnDy->DoGetValue().GetDouble(); + + m_nml->roi.from_string(m_propRoi->DoGetValue().GetString().ToStdString()); + + m_nml->patDims[0] = m_propPatW->DoGetValue().GetLong(); + m_nml->patDims[1] = m_propPatH->DoGetValue().GetLong(); + + m_nml->pctr[0] = m_propPcenX->DoGetValue().GetDouble(); + m_nml->pctr[1] = m_propPcenY->DoGetValue().GetDouble(); + m_nml->pctr[2] = m_propPcenZ->DoGetValue().GetDouble(); + switch(m_propVen->DoGetValue().GetLong()) { + case 0: m_nml->ven = "EMsoft"; break; + case 1: m_nml->ven = "Bruker"; break; + case 2: m_nml->ven = "Edax" ; break; + case 3: m_nml->ven = "Oxford"; break; + } + + m_nml->circRad = m_propCirc->DoGetValue().GetLong(); + m_nml->gausBckg = m_propBckg->DoGetValue().GetBool(); + m_nml->nRegions = m_propNreg->DoGetValue().GetLong(); + + m_nml->thetac = m_propThtC->DoGetValue().GetDouble(); + m_nml->delta = m_propDlt ->DoGetValue().GetDouble(); + + wxArrayString arr = m_propMP ->DoGetValue().GetArrayString(); + m_nml->patFile = m_propPatFile ->DoGetValue().GetString().ToStdString(); + m_nml->patName = m_propPatDset ->DoGetValue().GetString().ToStdString(); + m_nml->masterFiles.clear(); + for(size_t i = 0; i < arr.GetCount(); i++) m_nml->masterFiles.push_back(arr[i].ToStdString()); + + m_nml->dataFile = m_propDatafile->DoGetValue().GetString().ToStdString(); + m_nml->vendorFile = m_propVenFile ->DoGetValue().GetString().ToStdString(); + m_nml->ipfName = m_propIpf ->DoGetValue().GetString().ToStdString(); + m_nml->qualName = m_propQual ->DoGetValue().GetString().ToStdString(); + + m_nml->nThread = m_propThd ->DoGetValue().GetLong(); + m_nml->batchSize = m_propBatSz->DoGetValue().GetLong(); + } + + void EnableEditing(const bool enb) { + m_propInData ->Enable(enb); + m_patProc ->Enable(enb); + m_propCamCalib->Enable(enb); + m_propScanInfo->Enable(enb); + m_propIdxPrm ->Enable(enb); + m_propOutData ->Enable(enb); + } + + void EnableWizardEditing(const bool enb) { + m_propInData ->Enable(enb); + m_propPatSz ->Enable(enb); + m_propVen ->Enable(enb); + m_propPctr ->Enable(enb); + m_propScanInfo->Enable(enb); + } + + EbsdSummaryPanel( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 569,588 ), long style = wxTAB_TRAVERSAL, const wxString& name = wxEmptyString ); + ~EbsdSummaryPanel(); +}; + +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version Oct 26 2018) +// http://www.wxformbuilder.org/ +// +// PLEASE DO *NOT* EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// + +EbsdSummaryPanel::EbsdSummaryPanel( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name ) : ValidityPanel( parent, id, pos, size, style, name ) { + wxStaticBoxSizer* sbSum = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, wxT("Summary") ), wxVERTICAL ); + + wxPGChoices chs; + chs.Add("EMsoft", 0); + chs.Add("Bruker", 1); + chs.Add("Edax" , 2); + chs.Add("Oxford", 3); + wxString cmp(""); + + m_propGrid = new wxPropertyGrid(sbSum->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, wxPG_DEFAULT_STYLE|wxPG_SPLITTER_AUTO_CENTER|wxTAB_TRAVERSAL); + + //build input data + m_propInData = m_propGrid->Append ( new wxPropertyCategory ( wxT("Input Files" ), wxPG_LABEL ) ); + // m_propIpth = m_propGrid->AppendIn(m_propInData , new wxDirProperty ( wxT("ipath" ), wxPG_LABEL ) ); + m_propPatFile = m_propGrid->AppendIn(m_propInData , new wxFileProperty ( wxT("patfile" ), wxPG_LABEL ) ); + m_propPatDset = m_propGrid->AppendIn(m_propInData , new wxStringProperty ( wxT("patdset" ), wxPG_LABEL ) ); + m_propMP = m_propGrid->AppendIn(m_propInData , new wxArrayStringProperty( wxT("masterfile" ), wxPG_LABEL ) ); + // m_propPsym = m_propGrid->AppendIn(m_propInData , new wxFileProperty ( wxT("psymfile" ), wxPG_LABEL ) ); + + //build pattern processing + m_patProc = m_propGrid->Append ( new wxPropertyCategory ( wxT("Pattern Processing" ), wxPG_LABEL ) ); + m_propPatSz = m_propGrid->AppendIn(m_patProc , new wxStringProperty ( wxT("patdims" ), wxPG_LABEL, cmp ) ); + m_propPatW = m_propGrid->AppendIn(m_propPatSz , new wxUIntProperty ( wxT("w" ), wxT("pat_w" ) ) ); + m_propPatH = m_propGrid->AppendIn(m_propPatSz , new wxUIntProperty ( wxT("h" ), wxT("pat_h" ) ) ); + m_propCirc = m_propGrid->AppendIn(m_patProc , new wxIntProperty ( wxT("circmask" ), wxPG_LABEL ) ); + m_propBckg = m_propGrid->AppendIn(m_patProc , new wxBoolProperty ( wxT("gausbckg" ), wxPG_LABEL ) ); + m_propNreg = m_propGrid->AppendIn(m_patProc , new wxUIntProperty ( wxT("nregions" ), wxPG_LABEL ) ); + + //build camera calibration + m_propCamCalib = m_propGrid->Append ( new wxPropertyCategory ( wxT("Camera Calibration" ), wxPG_LABEL ) ); + m_propDlt = m_propGrid->AppendIn(m_propCamCalib, new wxFloatProperty ( wxT("delta" ), wxPG_LABEL ) ); + m_propVen = m_propGrid->AppendIn(m_propCamCalib, new wxEnumProperty ( wxT("vendor" ), wxPG_LABEL, chs ) ); + m_propPctr = m_propGrid->AppendIn(m_propCamCalib, new wxStringProperty ( wxT("pctr" ), wxPG_LABEL, cmp ) ); + m_propPcenX = m_propGrid->AppendIn(m_propPctr , new wxFloatProperty ( wxT("x" ), wxT("pctr_x" ) ) ); + m_propPcenY = m_propGrid->AppendIn(m_propPctr , new wxFloatProperty ( wxT("y" ), wxT("pctr_y" ) ) ); + m_propPcenZ = m_propGrid->AppendIn(m_propPctr , new wxFloatProperty ( wxT("z" ), wxT("pctr_z" ) ) ); + m_propThtC = m_propGrid->AppendIn(m_propCamCalib, new wxFloatProperty ( wxT("thetac" ), wxPG_LABEL ) ); + + //build scan dimensions + m_propScanInfo = m_propGrid->Append ( new wxPropertyCategory ( wxT("Scan Information" ), wxPG_LABEL ) ); + m_propScnSz = m_propGrid->AppendIn(m_propScanInfo, new wxStringProperty ( wxT("scandims" ), wxPG_LABEL, cmp ) ); + m_propScnW = m_propGrid->AppendIn(m_propScnSz , new wxUIntProperty ( wxT("w" ), wxT("scn_w" ) ) ); + m_propScnH = m_propGrid->AppendIn(m_propScnSz , new wxUIntProperty ( wxT("h" ), wxT("scn_h" ) ) ); + m_propScnDx = m_propGrid->AppendIn(m_propScnSz , new wxFloatProperty ( wxT("dx" ), wxT("scn_dx" ) ) ); + m_propScnDy = m_propGrid->AppendIn(m_propScnSz , new wxFloatProperty ( wxT("dy" ), wxT("scn_dy" ) ) ); + m_propRoi = m_propGrid->AppendIn(m_propScanInfo, new wxStringProperty ( wxT("roimask" ), wxPG_LABEL ) ); + + //build indexing parameters + m_propIdxPrm = m_propGrid->Append ( new wxPropertyCategory ( wxT("Indexing Parameters"), wxPG_LABEL ) ); + m_propBw = m_propGrid->AppendIn(m_propIdxPrm , new wxUIntProperty ( wxT("bw" ), wxPG_LABEL ) ); + m_propNrm = m_propGrid->AppendIn(m_propIdxPrm , new wxBoolProperty ( wxT("normed" ), wxPG_LABEL ) ); + m_propRef = m_propGrid->AppendIn(m_propIdxPrm , new wxBoolProperty ( wxT("refine" ), wxPG_LABEL ) ); + m_propThd = m_propGrid->AppendIn(m_propIdxPrm , new wxUIntProperty ( wxT("nthread" ), wxPG_LABEL ) ); + m_propBatSz = m_propGrid->AppendIn(m_propIdxPrm , new wxUIntProperty ( wxT("batchsize" ), wxPG_LABEL ) ); + + //build output data + m_propOutData = m_propGrid->Append ( new wxPropertyCategory ( wxT("Output Files" ), wxPG_LABEL ) ); + // m_propOpth = m_propGrid->AppendIn(m_propOutData , new wxDirProperty ( wxT("opath" ), wxPG_LABEL ) ); + m_propDatafile = m_propGrid->AppendIn(m_propOutData , new wxFileProperty ( wxT("datafile" ), wxPG_LABEL ) ); + m_propVenFile = m_propGrid->AppendIn(m_propOutData , new wxFileProperty ( wxT("vendorfile" ), wxPG_LABEL ) ); + m_propIpf = m_propGrid->AppendIn(m_propOutData , new wxFileProperty ( wxT("ipfmap" ), wxPG_LABEL ) ); + m_propQual = m_propGrid->AppendIn(m_propOutData , new wxFileProperty ( wxT("qualmap" ), wxPG_LABEL ) ); + + //formatting + m_propGrid->SetPropertyAttributeAll(wxPG_BOOL_USE_CHECKBOX, true);//use checkboxes instead of true/false dropdowns + + //final assmebly + sbSum->Add( m_propGrid, 1, wxALL|wxEXPAND, 5 ); + this->SetSizer( sbSum ); + this->Layout(); + + //connect events + m_propGrid->Connect( wxEVT_PG_CHANGED, wxPropertyGridEventHandler( EbsdSummaryPanel::PropChanged ), NULL, this ); +} + +EbsdSummaryPanel::~EbsdSummaryPanel() { + m_propGrid->Disconnect( wxEVT_PG_CHANGED, wxPropertyGridEventHandler( EbsdSummaryPanel::PropChanged ), NULL, this ); +} + +#endif//_EBSD_SUM_H_ diff --git a/include/wx/IdxParamPanel.h b/include/wx/IdxParamPanel.h new file mode 100644 index 0000000..a9a9114 --- /dev/null +++ b/include/wx/IdxParamPanel.h @@ -0,0 +1,228 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _IDX_PARAM_PAN_H_ +#define _IDX_PARAM_PAN_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wx/ValidityPanel.h" + +/////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +/// Class IdxParamPanel +/////////////////////////////////////////////////////////////////////////////// +class IdxParamPanel : public ValidityPanel +{ + private: + wxIntegerValidator valBw;//bandwidth + + protected: + wxTextCtrl * m_txtBw ; + wxButton * m_btnBwPrv; + wxCheckBox * m_chkNrm ; + wxCheckBox * m_chkRef ; + wxFilePickerCtrl* m_fpData ; + wxFilePickerCtrl* m_fpVen ; + wxFilePickerCtrl* m_fpIpf ; + wxFilePickerCtrl* m_fpCi ; + + // Virtual event handlers, overide them in your derived class + virtual void BandwidthChanged( wxCommandEvent & event ) { testValid(); } + virtual void OnPreview ( wxCommandEvent & event ) { event.Skip(); } + virtual void DataFileChanged ( wxFileDirPickerEvent& event ) { testValid(); } + + public: + + IdxParamPanel( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 400,300 ), long style = wxTAB_TRAVERSAL, const wxString& name = wxEmptyString ); + ~IdxParamPanel(); + + //@brief : get bandwidth + //@return: bandwidth + int getBw() const {long v; m_txtBw->GetLineText(0).ToLong(&v); return v;} + + //@brief : get normalized flag + //@return: normalized flag + bool getNorm() const {return m_chkNrm->GetValue();} + + //@brief : get refinement flag + //@return: refinement flag + bool getRef() const {return m_chkRef->GetValue();} + + //@brief : get the data file + //@return: data file + wxString getDataFile() const {return m_fpData->GetPath();} + + //@brief : get the vendor file + //@return: vendor file + wxString getVendorFile() const {return m_fpVen->GetPath();} + + //@brief : get the ipf file + //@return: ipf file + wxString getIpfFile() const {return m_fpIpf->GetPath();} + + //@brief : get the CI file + //@return: CI file + wxString getCiFile() const {return m_fpCi->GetPath();} + + //require bandwidth + output file + + //@brief: sanity check the current state + //@return: true if the values parsed from the panel are reasonable, false otherwise + //@note : checks for has a file, has detector sizes, and has an AHE value + std::string validMsg() const { + if(m_txtBw->GetLineText(0).IsEmpty()) return "bandwidth empty"; + if(getDataFile().IsEmpty()) return "data file empty"; + return ""; + } + +}; + +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version Oct 26 2018) +// http://www.wxformbuilder.org/ +// +// PLEASE DO *NOT* EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// + +IdxParamPanel::IdxParamPanel( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name ) : ValidityPanel( parent, id, pos, size, style, name ) { + //split panel into 2 vertical boxes + wxBoxSizer * bIdxMisc = new wxBoxSizer( wxVERTICAL ); + wxStaticBoxSizer* sbIdxPrm = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, wxT("Indexing Parameters") ), wxVERTICAL ); + wxStaticBoxSizer* sbOutFile = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, wxT("Output Files" ) ), wxVERTICAL ); + bIdxMisc->Add( sbIdxPrm , 1, wxEXPAND, 5 ); + bIdxMisc->Add( sbOutFile, 2, wxEXPAND, 5 ); + + //top box is 6x2 grid + wxFlexGridSizer* fgIdxPrm = new wxFlexGridSizer( 2, 6, 0, 0 ); + fgIdxPrm->AddGrowableCol( 0 ); fgIdxPrm->AddGrowableCol( 2 ); fgIdxPrm->AddGrowableCol( 5 ); + fgIdxPrm->AddGrowableRow( 0 ); fgIdxPrm->AddGrowableRow( 1 ); + fgIdxPrm->SetFlexibleDirection( wxHORIZONTAL ); + fgIdxPrm->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); + sbIdxPrm->Add( fgIdxPrm, 1, wxEXPAND, 5 ); + + //bottom box is 4x2 grid + wxFlexGridSizer* fgOutFile = new wxFlexGridSizer( 4, 2, 0, 0 ); + fgOutFile->AddGrowableCol( 1 ); + fgOutFile->AddGrowableRow( 0 ); fgOutFile->AddGrowableRow( 1 ); fgOutFile->AddGrowableRow( 2 ); fgOutFile->AddGrowableRow( 3 ); + fgOutFile->SetFlexibleDirection( wxHORIZONTAL ); + fgOutFile->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); + sbOutFile->Add( fgOutFile, 1, wxEXPAND, 5 ); + + //build all labels + wxStaticText* staTxtBw = new wxStaticText( sbIdxPrm ->GetStaticBox(), wxID_ANY, wxT("Bandwidth" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtBw ->Wrap(-1); + wxStaticText* staTxtDataFile = new wxStaticText( sbOutFile->GetStaticBox(), wxID_ANY, wxT("Data File" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtDataFile->Wrap(-1); + wxStaticText* staTxtVenFile = new wxStaticText( sbOutFile->GetStaticBox(), wxID_ANY, wxT("Vendor File"), wxDefaultPosition, wxDefaultSize, 0 ); staTxtVenFile ->Wrap(-1); + wxStaticText* staTxtIpf = new wxStaticText( sbOutFile->GetStaticBox(), wxID_ANY, wxT("IPF Map" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtIpf ->Wrap(-1); + wxStaticText* staTxtCi = new wxStaticText( sbOutFile->GetStaticBox(), wxID_ANY, wxT("CI Map" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtCi ->Wrap(-1); + + //set text validator ranges + valBw.SetRange(1, 350); + + //build elements for indexing parameters box + m_txtBw = new wxTextCtrl ( sbIdxPrm ->GetStaticBox(), wxID_ANY, wxT("" ), wxDefaultPosition, wxDefaultSize, wxTE_CENTER, valBw ); + m_btnBwPrv = new wxButton ( sbIdxPrm ->GetStaticBox(), wxID_ANY, wxT("Preview..."), wxDefaultPosition, wxDefaultSize, 0 ); + m_chkNrm = new wxCheckBox ( sbIdxPrm ->GetStaticBox(), wxID_ANY, wxT("Normalized"), wxDefaultPosition, wxDefaultSize, 0 ); + m_chkRef = new wxCheckBox ( sbIdxPrm ->GetStaticBox(), wxID_ANY, wxT("Refinement"), wxDefaultPosition, wxDefaultSize, 0 ); + m_btnBwPrv->Enable(false); + + //build elements for output file box + m_fpData = new wxFilePickerCtrl( sbOutFile->GetStaticBox(), wxID_ANY, wxEmptyString, wxT("Select a file"), wxT("*.h5" ), wxDefaultPosition, wxDefaultSize, wxFLP_OVERWRITE_PROMPT|wxFLP_SAVE|wxFLP_SMALL|wxFLP_USE_TEXTCTRL ); + m_fpVen = new wxFilePickerCtrl( sbOutFile->GetStaticBox(), wxID_ANY, wxEmptyString, wxT("Select a file"), wxT("*.ang;*.ctf"), wxDefaultPosition, wxDefaultSize, wxFLP_OVERWRITE_PROMPT|wxFLP_SAVE|wxFLP_SMALL|wxFLP_USE_TEXTCTRL ); + m_fpIpf = new wxFilePickerCtrl( sbOutFile->GetStaticBox(), wxID_ANY, wxEmptyString, wxT("Select a file"), wxT("*.png" ), wxDefaultPosition, wxDefaultSize, wxFLP_SAVE|wxFLP_SMALL|wxFLP_USE_TEXTCTRL ); + m_fpCi = new wxFilePickerCtrl( sbOutFile->GetStaticBox(), wxID_ANY, wxEmptyString, wxT("Select a file"), wxT("*.png" ), wxDefaultPosition, wxDefaultSize, wxFLP_SAVE|wxFLP_SMALL|wxFLP_USE_TEXTCTRL ); + + //assemble indexing parameter grid + fgIdxPrm ->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgIdxPrm ->Add( staTxtBw , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgIdxPrm ->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgIdxPrm ->Add( m_txtBw , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgIdxPrm ->Add( m_btnBwPrv , 0, wxALL|wxALIGN_CENTER_VERTICAL , 5 ); + fgIdxPrm ->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgIdxPrm ->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgIdxPrm ->Add( m_chkNrm , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgIdxPrm ->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgIdxPrm ->Add( m_chkRef , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgIdxPrm ->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgIdxPrm ->Add( 0 , 0, 1 , wxEXPAND , 5 ); + + //assemble output file grid + fgOutFile->Add( staTxtDataFile, 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgOutFile->Add( m_fpData , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxEXPAND , 5 ); + fgOutFile->Add( staTxtVenFile , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgOutFile->Add( m_fpVen , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxEXPAND , 5 ); + fgOutFile->Add( staTxtIpf , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgOutFile->Add( m_fpIpf , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxEXPAND , 5 ); + fgOutFile->Add( staTxtCi , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgOutFile->Add( m_fpCi , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxEXPAND , 5 ); + + this->SetSizer( bIdxMisc ); + this->Layout(); + + // Connect Events + m_txtBw ->Connect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler ( IdxParamPanel::BandwidthChanged ), NULL, this ); + m_btnBwPrv->Connect( wxEVT_COMMAND_BUTTON_CLICKED , wxCommandEventHandler ( IdxParamPanel::OnPreview ), NULL, this ); + m_fpData ->Connect( wxEVT_COMMAND_FILEPICKER_CHANGED, wxFileDirPickerEventHandler( IdxParamPanel::DataFileChanged ), NULL, this ); +} + +IdxParamPanel::~IdxParamPanel() { + // Disconnect Events + m_txtBw ->Disconnect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler ( IdxParamPanel::BandwidthChanged ), NULL, this ); + m_btnBwPrv->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED , wxCommandEventHandler ( IdxParamPanel::OnPreview ), NULL, this ); + m_fpData ->Disconnect( wxEVT_COMMAND_FILEPICKER_CHANGED, wxFileDirPickerEventHandler( IdxParamPanel::DataFileChanged ), NULL, this ); +} + +#endif//_IDX_PARAM_PAN_H_ diff --git a/include/wx/ImagePanel.h b/include/wx/ImagePanel.h new file mode 100644 index 0000000..492fba8 --- /dev/null +++ b/include/wx/ImagePanel.h @@ -0,0 +1,253 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _IMAGE_PANEL_H +#define _IMAGE_PANEL_H + +#include +#include +#include +#include + +//@brief: helper class to dynamically draw a rescaled image into a frame (preserving aspect ratio) +//@note : based on //https://wiki.wxwidgets.org/An_image_panel +class wxImagePanel : public wxPanel { + wxImage image ;//the original image + wxBitmap resized ;//the resized image (to render) + int curW, curH;//the current width/height of the resized image + bool stale ;//is a redraw required even if the size hasn't changed (e.g. the image was changed) + wxPoint ori ;//current origin of image + + public: + //@brief : constructor, mirrors signature of wxPanel constructor + //@param parent: The parent window. + //@param id : An identifier for the panel. wxID_ANY is taken to mean a default. + //@param pos : The panel position. The value wxDefaultPosition indicates a default position, chosen by either the windowing system or wxWidgets, depending on platform. + //@param size : The panel size. The value wxDefaultSize indicates a default size, chosen by either the windowing system or wxWidgets, depending on platform. + //@param style : The window style. See wxPanel. + //@param name : Window name. + wxImagePanel(wxWindow *parent, wxWindowID id=wxID_ANY, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=wxTAB_TRAVERSAL, const wxString &name=wxPanelNameStr); + + //@brief : update the image to draw + //@param im: new image + void setImage(wxImage& im) {image = im.Copy(); if(!im.HasAlpha()) image.SetAlpha(); stale = true;} + + //@brief : check if there is an image + //@return: true if there is an image, false otherwise + bool hasImage() const {return image.IsOk();} + + //@brief : update a single pixel of the image + //@param x: x index of pixel to update + //@param y: y index of pixel to update + //@param r: red value + //@param g: green value + //@param b: blue value + void SetRGB(const size_t x, const size_t y, const char r, const char g, const char b) {image.SetRGB(x, y, r, g, b); stale = true;} + + //@brief : update a single pixel of the image + //@param x: x index of pixel to update + //@param y: y index of pixel to update + //@param v: gray value + void SetGray(const size_t x, const size_t y, const char v) {SetRGB(x, y, v, v, v);} + + //@brief : update a single pixel of the image + //@param x: x index of pixel to update + //@param y: y index of pixel to update + //@param v: alpha value + void SetAlpha(const size_t x, const size_t y, const char v) {image.SetAlpha(x, y, v); stale = true;} + + //@brief: manually mark the image as outdated + void markStale() {stale = true;} + + //@brief : get the underlying rgb buffer + //@return: rgb buffer (you are responsible for calling markStale() if you change values) + unsigned char* GetRGB() {return image.GetData();} + + //@brief : get the underlying alpha buffer + //@return: alpha buffer (you are responsible for calling markStale() if you change values) + unsigned char* GetAlpha() {return image.GetAlpha();} + + //@brief : get original (unscaled) image size + //@return: image size + wxSize GetSize() const {return image.IsOk() ? image.GetSize() : wxSize(0, 0);} + + //@brief : get the current origin of the image + //@return: image origin + wxPoint getOri() const {return ori;} + + //@brief : get the current scale of the image + //@return: image scaling + double getScl() const {return image.IsOk() ? double(curW) / image.GetSize().GetWidth() : 0.0;} + + //@brief : get the current scale of the image^2 + //@return: image scaling^2 + double getScl2() const {return image.IsOk() ? double(curW * curH) / (image.GetSize().GetWidth() * image.GetSize().GetHeight()): 0.0;} + + //@brief : convert from image coordinates to DC coordinates + //@param x: x coordinate to convert from image -> panel coordinates + //@param y: x coordinate to convert from image -> panel coordinates + void im2dc(int& x, int& y) const; + + //@brief: convert from image coordinates to DC coordinates + //@param x: x coordinate to convert from panel -> image coordinates + //@param y: x coordinate to convert from panel -> image coordinates + void dc2im(int& x, int& y) const; + + //@brief : actual code to draw the image + //@param dc: device context to draw with + //@note : seperate function to handle different context types + void render(wxDC& dc); + + //@brief : called by wxWidgets event loop to redraw + //@param evt: redraw event + //@note : triggered by Refresh()/Update() + void paintEvent(wxPaintEvent & evt); + + //@brief: manually redraw + void paintNow(); + + //@brief : event for image resizing (refresh panel) + //@param event: resize event + void OnSize(wxSizeEvent& event); + + DECLARE_EVENT_TABLE() +}; + +BEGIN_EVENT_TABLE(wxImagePanel, wxPanel) + EVT_PAINT(wxImagePanel::paintEvent)// paint events + EVT_SIZE (wxImagePanel::OnSize )// size event +END_EVENT_TABLE() + +//@brief : image panel constructor, mirrors signature of wxPanel constructor +//@param parent: The parent window. +//@param id : An identifier for the panel. wxID_ANY is taken to mean a default. +//@param pos : The panel position. The value wxDefaultPosition indicates a default position, chosen by either the windowing system or wxWidgets, depending on platform. +//@param size : The panel size. The value wxDefaultSize indicates a default size, chosen by either the windowing system or wxWidgets, depending on platform. +//@param style : The window style. See wxPanel. +//@param name : Window name. +wxImagePanel::wxImagePanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style, const wxString &name) : wxPanel(parent, id, pos, size, style, name), image(), curW(-1), curH(-1), stale(true) { + SetBackgroundColour( parent->GetBackgroundColour() );//transparent background +} + +//@brief : convert from image coordinates to DC coordinates +//@param x: x coordinate to convert from image -> panel coordinates +//@param y: x coordinate to convert from image -> panel coordinates +void wxImagePanel::im2dc(int& x, int& y) const { + x = (int)std::round( double(x * curW) / image.GetSize().GetWidth () ) + ori.x; + y = (int)std::round( double(y * curH) / image.GetSize().GetHeight() ) + ori.y; +} + +//@brief: convert from image coordinates to DC coordinates +//@param x: x coordinate to convert from panel -> image coordinates +//@param y: x coordinate to convert from panel -> image coordinates +void wxImagePanel::dc2im(int& x, int& y)const { + x = (int) std::round( double( (x - ori.x) * image.GetSize().GetWidth () ) / curW ); + y = (int) std::round( double( (y - ori.y) * image.GetSize().GetHeight() ) / curH ); +} + +//@brief : actual code to draw the image +//@param dc: device context to draw with +//@note : seperate function to handle different context types +void wxImagePanel::render(wxDC& dc) { + //we we don't have an image we're done + if(!image.IsOk()) return; + + //get frame size + int dcW, dcH; + wxWindowDC(this).GetSize( &dcW, &dcH ); + // dc.GetSize( &dcW, &dcH );//this doesn't awlays update correctly for all sizes on windows + + //get image size + int imW = image.GetSize().GetWidth (); + int imH = image.GetSize().GetHeight(); + + //get max image size that fits in frame with same aspect ratio + int newW = dcW; + int newH = dcH; + float rW = float(newW) / imW; + float rH = float(newH) / imH; + if(rW <= rH) { + newH = (int) std::round(rW * imH); + } else { + newW = (int) std::round(rH * imW); + } + + //update bitmap if needed + if(newW > 0 && newH > 0) { + + if( newW != curW || newH != curH || stale) { + if(0 == newW || 0 == newH) { + resized = wxBitmap();//rescaled to nothing + } else { + resized = wxBitmap( image.Scale( newW, newH /*, wxIMAGE_QUALITY_HIGH*/ ) ); + } + curW = newW; + curH = newH; + stale = false; + } + } + + //draw + int dx = (dcW - newW) / 2; + int dy = (dcH - newH) / 2; + ori.x = dx; + ori.y = dy; + if(curW == 0 || curH == 0) return;//nothing to draw + dc.DrawBitmap( resized, dx, dy, false ); +} + +//@brief : called by wxWidgets event loop to redraw +//@param evt: redraw event +//@note : triggered by Refresh()/Update() +void wxImagePanel::paintEvent(wxPaintEvent & evt) { + // depending on your system you may need to look at double-buffered dcs + wxPaintDC dc(this); + render(dc); +} + +//@brief: manually redraw +void wxImagePanel::paintNow() { + // depending on your system you may need to look at double-buffered dcs + wxClientDC dc(this); + render(dc); +} + +//@brief : event for image resizing (refresh panel) +//@param event: resize event +void wxImagePanel::OnSize(wxSizeEvent& event){ + Refresh(); + event.Skip();//skip the event. +} + +#endif//_IMAGE_PANEL_H diff --git a/include/wx/IndexingFrame.h b/include/wx/IndexingFrame.h new file mode 100644 index 0000000..8717a94 --- /dev/null +++ b/include/wx/IndexingFrame.h @@ -0,0 +1,279 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _IDX_FRAME_H +#define _IDX_FRAME_H + +#include +#include +#include +#include +#include +#include +#include + +#include "ImagePanel.h" +#include "EbsdSummaryPanel.h" + +#include "modality/ebsd/nml.hpp" +#include "modality/ebsd/idx.hpp" +#include "util/timer.hpp" + +/////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +/// Class IndexingFrame +/////////////////////////////////////////////////////////////////////////////// + +BEGIN_DECLARE_EVENT_TYPES() + DECLARE_EVENT_TYPE(wxEVT_IndexingThread, -1)//type for thread to communicate with frame +END_DECLARE_EVENT_TYPES() + +class IndexingFrame : public wxFrame { + private: + std::thread idxThd ;//thread for indexing + std::atomic_flag flg ;//flag to tell worker thread to stop + emsphinx::ebsd::Namelist nml ;//namelist + wxImage imOrig ;//original image (e.g. iq) + wxImage imPaint;//ipf painted image + + enum IdxStatus { + IdxError = - 1, + IdxExit = - 2, + IdxCmplt = 101, + }; + + + protected: + wxSplitterWindow* m_split ; + wxImagePanel * m_imPan ; + EbsdSummaryPanel* m_sumPan ; + wxGauge * m_prog ; + wxButton * m_btn ; + wxStatusBar * m_statBar; + + virtual void OnBtn( wxCommandEvent& event ) { + if("Start" == m_btn->GetLabel()) startIdx(); + else stopIdx(); + } + + void OnThread(wxCommandEvent& event) { + //update the status bar and get event type + m_statBar->SetStatusText(event.GetString()); + int i = event.GetInt(); + + //handle event type + if(IdxError == i) {//an error + //nothing else to do for now (wait for exit message) + } else if(IdxCmplt == i || IdxExit == i) {//(un)successful indexing completion + idxThd.join();//wait for the thread to wrap up + m_sumPan->EnableEditing(true);//enable the parameter panel + m_btn->SetLabel("Start");//change stop button to a start button again + m_btn->Enable(true); + } else {//status update + m_btn->Enable(true); + m_prog->SetValue(event.GetInt());//update progress bar + m_imPan->setImage(imPaint);//update image + m_imPan->Refresh();//repaint + m_imPan->paintNow(); + } + } + + public: + + void startIdx(); + + void stopIdx(); + + void setNml(emsphinx::ebsd::Namelist& n) {nml = n; m_sumPan->setNamelist(&nml);} + + void setImage(wxImage& im) {m_imPan->setImage(im); imOrig = im; imPaint = im.Copy();} + + IndexingFrame( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = "EMSphInx", const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 720,540 ), long style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL ); + + ~IndexingFrame(); + + wxDECLARE_EVENT_TABLE(); +}; + +BEGIN_EVENT_TABLE(IndexingFrame, wxFrame) + EVT_COMMAND(wxID_ANY, wxEVT_IndexingThread, IndexingFrame::OnThread) +END_EVENT_TABLE() + +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version Oct 26 2018) +// http://www.wxformbuilder.org/ +// +// PLEASE DO *NOT* EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// + +DEFINE_EVENT_TYPE(wxEVT_IndexingThread) + +void IndexingFrame::startIdx() { + imPaint = imOrig.Copy(); + m_sumPan->EnableEditing(false);//disable the parameter panel + m_sumPan->updateNamelist(); + m_btn->SetLabel("Stop");//change start button to stop button + m_btn->Enable(false); + + flg.test_and_set(); + idxThd = std::thread([&](){ + //create event for dispatching + wxCommandEvent evt(wxEVT_IndexingThread); + evt.SetInt(0); + evt.SetString("Initializing Indexers"); + wxPostEvent(this, evt);//update image + progress bar + try { + emsphinx::ebsd::IndexingData idxData(nml, (char*)imPaint.GetData());//read inputs and build indexers + ThreadPool pool(idxData.threadCount);//build thread pool + try { + //queue parallel indexing + time_t tmStart = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + Timer t; + size_t batches = idxData.pat->numPat() / nml.batchSize;//how many batches are needed + if(batches * nml.batchSize < idxData.pat->numPat()) ++batches;//extra batch for leftovers + for(size_t i = 0; i < batches; i++) {//loop over batches + const size_t start = i * nml.batchSize;//first pattern + const size_t end = std::min(start + nml.batchSize, idxData.pat->numPat());//last pattern + pool.schedule(std::bind(idxData.workItem, std::placeholders::_1));//queue indexing + } + + //wait for completion posting updates periodically + const std::chrono::milliseconds uptFreq(250);//milliseconds between updates (too long -> unresponsive, too short -> resource intensive), 0.25 ~human reaction time + while(!pool.waitAll(uptFreq)) { + if(!flg.test_and_set()) throw std::runtime_error("Stop Requested"); + + //get the time elapsed since we started (without resetting the reference point) + const double elapsed = t.poll(false); + const double percent = double(idxData.idxCtr) / idxData.numIdx; + const double rate = elapsed / percent;//estimate seconds per % + const double remaining = rate * (1.0 - percent);//estimate seconds left + + //print update + std::ostringstream ss; + Timer::PrintSeconds(elapsed , ss); + ss << " elapsed, "; + Timer::PrintSeconds(remaining, ss); + ss << " remaining "; + evt.SetString(ss.str()); + evt.SetInt(std::min((int)(percent * 100 + 0.5), 100)); + wxPostEvent(this, evt); + } + + //compute indexing time and save + const double total = t.poll(); + time_t tmEnd = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + idxData.save(tmStart, tmEnd, total); + + //post completion status + evt.SetString("Indexing Complete"); + evt.SetInt(100); + wxPostEvent(this, evt);//post status update with 100% completion + evt.SetInt(IdxCmplt); + wxPostEvent(this, evt);//post actual completion message + return; + } catch (std::exception& e) {//clear items and post + pool.clear();//clear all unstarted items + //post error message + evt.SetString(e.what()); + evt.SetInt(IdxError); + wxPostEvent(this, evt); + } + //pool goes out of scope here (waiting for completion) + } catch (std::exception& e) { + //post error message + evt.SetString(e.what()); + evt.SetInt(IdxError); + wxPostEvent(this, evt); + } + + //post exit message + evt.SetString("Indexing Stopped"); + evt.SetInt(0); + wxPostEvent(this, evt);//update image + progress bar + evt.SetInt(IdxExit); + wxPostEvent(this, evt);//mark exit + }); +} + +void IndexingFrame::stopIdx() { + flg.clear();//tell any running indexers to stop + m_sumPan->EnableEditing(true);//enable the parameter panel + m_btn->Enable(false);//disable the start/stop button +} + +IndexingFrame::IndexingFrame( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxFrame( parent, id, title, pos, size, style ) { + this->SetSizeHints( wxDefaultSize, wxDefaultSize ); + + //create splitter window + m_split = new wxSplitterWindow( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_3DSASH|wxSP_LIVE_UPDATE ); + m_split->SetSashGravity( 1 );//put all growth into left side + m_split->SetMinimumPaneSize( 50 ); + + //create image + indexing parameters and add to splitter + m_imPan = new wxImagePanel ( m_split, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + m_sumPan = new EbsdSummaryPanel( m_split, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + m_split->SplitVertically(m_imPan, m_sumPan); + + //create sizers and assemble + wxBoxSizer* vSizer = new wxBoxSizer( wxVERTICAL ); + wxBoxSizer* hSizer = new wxBoxSizer( wxHORIZONTAL ); + vSizer->Add( m_split, 1, wxEXPAND, 5 ); + vSizer->Add( hSizer , 0, wxEXPAND, 5 ); + + //build and assemble controls + m_prog = new wxGauge ( this, wxID_ANY, 100 , wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL ); + m_btn = new wxButton ( this, wxID_ANY, wxT("Start"), wxDefaultPosition, wxDefaultSize, 0 ); + m_prog->SetValue( 0 ); + m_prog->SetRange( 100 ); + hSizer->Add( m_prog , 1, wxEXPAND, 0 ); + hSizer->Add( m_btn , 0, wxEXPAND, 0 ); + + //layout + this->SetSizer( vSizer ); + this->Layout(); + m_statBar = this->CreateStatusBar( 1, wxSTB_ELLIPSIZE_END|wxSTB_SIZEGRIP, wxID_ANY ); + this->Centre( wxBOTH ); + + m_btn->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( IndexingFrame::OnBtn ), NULL, this ); +} + +IndexingFrame::~IndexingFrame() { + m_btn->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( IndexingFrame::OnBtn ), NULL, this ); +} + +#endif//_IDX_FRAME_H + diff --git a/include/wx/MasterFileFilterPanel.h b/include/wx/MasterFileFilterPanel.h new file mode 100644 index 0000000..b10b2d4 --- /dev/null +++ b/include/wx/MasterFileFilterPanel.h @@ -0,0 +1,290 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _MP_FILT_PAN_H_ +#define _MP_FILT_PAN_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/////////////////////////////////////////////////////////////////////////// + +#include "PeriodicTablePanel.h" + +/////////////////////////////////////////////////////////////////////////////// +/// Class MasterFileFilterPanel +/////////////////////////////////////////////////////////////////////////////// +class MasterFileFilterPanel : public wxPanel +{ + private: + wxFloatingPointValidator valKv ; + wxFloatingPointValidator valTlt; + wxIntegerValidator valSg ; + + protected: + wxCheckBox * m_chkKv ; + wxTextCtrl * m_kvMin ; + wxTextCtrl * m_kvMax ; + wxCheckBox * m_chkTlt; + wxTextCtrl * m_tltMin; + wxTextCtrl * m_tltMax; + wxCheckBox * m_chkSg ; + wxTextCtrl * m_sgMin ; + wxTextCtrl * m_sgMax ; + wxChoice * m_laue ; + PeriodicTablePanel* m_prdTbl; + + void ChoiceChanged( wxCommandEvent& event ); + + public: + //@brief : set filter bounds + //@param kv : upper and lower bounds for kv filtering + //@param tlt: upper and lower bounds for tilt filtering + //@param el : bitmask of elements + //@param sg : upper and lower bounds for space group filtering + void setBounds(std::pair kv, std::pair tlt, ElementMask el, std::pair sg); + + //@brief : get filter bounds + //@param kv : location to write upper and lower bounds for kv filtering + //@param tlt: location to write upper and lower bounds for tilt filtering + //@param el : location to write bitmask of elements + //@param sg : location to write upper and lower bounds for space group filtering + void getBounds(std::pair& kv, std::pair& tlt, ElementMask& el, std::pair& sg) const; + + MasterFileFilterPanel( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 695,300 ), long style = wxTAB_TRAVERSAL, const wxString& name = wxEmptyString ); + ~MasterFileFilterPanel(); + +}; + + +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version Oct 26 2018) +// http://www.wxformbuilder.org/ +// +// PLEASE DO *NOT* EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// + +//@brief : set filter bounds +//@param kv : upper and lower bounds for kv filtering +//@param tlt: upper and lower bounds for tilt filtering +//@param el : bitmask of elements +//@param sg : upper and lower bounds for space group filtering +void MasterFileFilterPanel::setBounds(std::pair kv, std::pair tlt, ElementMask el, std::pair sg) { + if(kv .first == kv .first) {//not nan + m_chkKv ->SetValue(true); + m_kvMin ->Clear(); m_kvMin ->operator<<(kv .first ); + m_kvMax ->Clear(); m_kvMax ->operator<<(kv .second); + } + + if(tlt.first == tlt.first) {//not nan + m_chkTlt->SetValue(true); + m_tltMin->Clear(); m_tltMin->operator<<(tlt.first ); + m_tltMax->Clear(); m_tltMax->operator<<(tlt.second); + } + + if(sg.first > 1 || sg.second < 230) { + m_chkSg ->SetValue(true); + m_sgMin ->Clear(); m_sgMin ->operator<<(sg .first ); + m_sgMax ->Clear(); m_sgMax ->operator<<(sg .second); + + //check if this range corresponds to a laue group + if( 1 == sg.first && 2 == sg.second) m_laue->SetSelection( 0); + if( 3 == sg.first && 15 == sg.second) m_laue->SetSelection( 1); + if( 16 == sg.first && 74 == sg.second) m_laue->SetSelection( 2); + if( 75 == sg.first && 88 == sg.second) m_laue->SetSelection( 3); + if( 89 == sg.first && 142 == sg.second) m_laue->SetSelection( 4); + if(143 == sg.first && 148 == sg.second) m_laue->SetSelection( 5); + if(149 == sg.first && 167 == sg.second) m_laue->SetSelection( 6); + if(168 == sg.first && 176 == sg.second) m_laue->SetSelection( 7); + if(177 == sg.first && 194 == sg.second) m_laue->SetSelection( 8); + if(195 == sg.first && 206 == sg.second) m_laue->SetSelection( 9); + if(207 == sg.first && 230 == sg.second) m_laue->SetSelection(10); + } + + m_prdTbl->setMask(el); +} + +//@brief : get filter bounds +//@param kv : location to write upper and lower bounds for kv filtering +//@param tlt: location to write upper and lower bounds for tilt filtering +//@param el : location to write bitmask of elements +//@param sg : location to write upper and lower bounds for space group filtering +void MasterFileFilterPanel::getBounds(std::pair& kv, std::pair& tlt, ElementMask& el, std::pair& sg) const { + if(m_chkKv ->GetValue()) { + double vMin, vMax; + m_kvMin ->GetLineText(0).ToDouble(&vMin); + m_kvMax ->GetLineText(0).ToDouble(&vMax); + kv .first = (int)vMin; + kv .second = (int)vMax; + } else { + kv.first = kv.second = NAN; + } + + if(m_chkTlt->GetValue()) { + double vMin, vMax; + m_tltMin->GetLineText(0).ToDouble(&vMin); + m_tltMax->GetLineText(0).ToDouble(&vMax); + tlt.first = (int)vMin; + tlt.second = (int)vMax; + } else { + tlt.first = tlt.second = NAN; + } + + if(m_chkSg ->GetValue()) { + long vMin, vMax; + m_sgMin ->GetLineText(0).ToLong(&vMin); + m_sgMax ->GetLineText(0).ToLong(&vMax); + sg .first = (int)vMin; + sg .second = (int)vMax; + } else { + sg.first = 1; sg.second = 230; + } + + el = m_prdTbl->getMask(); +} + +void MasterFileFilterPanel::ChoiceChanged( wxCommandEvent& event ) { + int vMin = 1, vMax = 230; + switch(event.GetInt()) { + case 0: vMin = 1, vMax = 2; break; + case 1: vMin = 3, vMax = 15; break; + case 2: vMin = 16, vMax = 74; break; + case 3: vMin = 75, vMax = 88; break; + case 4: vMin = 89, vMax = 142; break; + case 5: vMin = 143, vMax = 148; break; + case 6: vMin = 149, vMax = 167; break; + case 7: vMin = 168, vMax = 176; break; + case 8: vMin = 177, vMax = 194; break; + case 9: vMin = 195, vMax = 206; break; + case 10: vMin = 207, vMax = 230; break; + } + if(vMin > 1 || vMax < 230) { + m_chkSg ->SetValue(true); + m_sgMin ->Clear(); m_sgMin ->operator<<(vMin); + m_sgMax ->Clear(); m_sgMax ->operator<<(vMax); + } +} + +MasterFileFilterPanel::MasterFileFilterPanel( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name ) : wxPanel( parent, id, pos, size, style, name ) { + //build sizers + wxBoxSizer * bSizer = new wxBoxSizer( wxVERTICAL ); + wxFlexGridSizer* fgSizer = new wxFlexGridSizer( 3, 5, 0, 0 ); + fgSizer->AddGrowableCol( 0 ); fgSizer->AddGrowableCol( 1 ); fgSizer->AddGrowableCol( 2 ); fgSizer->AddGrowableCol( 3 ); fgSizer->AddGrowableCol( 4 ); + fgSizer->AddGrowableRow( 0 ); fgSizer->AddGrowableRow( 1 ); fgSizer->AddGrowableRow( 2 ); + fgSizer->SetFlexibleDirection( wxHORIZONTAL ); + fgSizer->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); + + //build labels + wxStaticText* dash1 = new wxStaticText( this, wxID_ANY, wxT("-"), wxDefaultPosition, wxDefaultSize, 0 ); + wxStaticText* dash2 = new wxStaticText( this, wxID_ANY, wxT("-"), wxDefaultPosition, wxDefaultSize, 0 ); + wxStaticText* dash3 = new wxStaticText( this, wxID_ANY, wxT("-"), wxDefaultPosition, wxDefaultSize, 0 ); + + //build validators + valKv .SetRange(0.0f, 50.0f); + valTlt.SetRange(0.0f, 90.0f); + valSg .SetRange(1 , 230 ); + valKv .SetPrecision(1); + valTlt.SetPrecision(1); + + //build elements + wxString choices[] = { + wxT("Triclinic (-1) [1, 2]"), + wxT("Monoclinic (2/m) [3, 15]"), + wxT("Orthorhombic (mmm) [16, 74]"), + wxT("Tetragonal Low (4/m) [75, 88]"), + wxT("Tetragonal High (4/mmm) [89, 142]"), + wxT("Triclinic Low (-3) [143, 148]"), + wxT("Triclinic High (3/m) [149, 167]"), + wxT("Hexagonal Low (6/m) [168, 176]"), + wxT("Hexagaonl High (6/mmm) [177, 194]"), + wxT("Cubic Low (m-3) [195, 206]"), + wxT("Cubic High (m-3m) [207, 230]") + }; + m_chkKv = new wxCheckBox ( this, wxID_ANY, wxT("kV" ), wxDefaultPosition, wxDefaultSize, 0 ); + m_kvMin = new wxTextCtrl ( this, wxID_ANY, wxT("0.0" ), wxDefaultPosition, wxDefaultSize, 0 , valKv ); + m_kvMax = new wxTextCtrl ( this, wxID_ANY, wxT("50.0" ), wxDefaultPosition, wxDefaultSize, 0 , valKv ); + m_chkTlt = new wxCheckBox ( this, wxID_ANY, wxT("Tilt" ), wxDefaultPosition, wxDefaultSize, 0 ); + m_tltMin = new wxTextCtrl ( this, wxID_ANY, wxT("0.0" ), wxDefaultPosition, wxDefaultSize, 0 , valTlt ); + m_tltMax = new wxTextCtrl ( this, wxID_ANY, wxT("90.0" ), wxDefaultPosition, wxDefaultSize, 0 , valTlt ); + m_chkSg = new wxCheckBox ( this, wxID_ANY, wxT("Space Group"), wxDefaultPosition, wxDefaultSize, 0 ); + m_sgMin = new wxTextCtrl ( this, wxID_ANY, wxT("1" ), wxDefaultPosition, wxDefaultSize, 0 , valSg ); + m_sgMax = new wxTextCtrl ( this, wxID_ANY, wxT("230" ), wxDefaultPosition, wxDefaultSize, 0 , valSg ); + m_laue = new wxChoice ( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 11, choices, 0 ); + m_prdTbl = new PeriodicTablePanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + m_laue->SetSelection( -1 ); + + //assemble grid + fgSizer->Add( m_chkKv , 0, wxALL|wxALIGN_CENTER_VERTICAL , 5 ); + fgSizer->Add( m_kvMin , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgSizer->Add( dash1 , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgSizer->Add( m_kvMax , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgSizer->AddStretchSpacer(); + + fgSizer->Add( m_chkTlt, 0, wxALL|wxALIGN_CENTER_VERTICAL , 5 ); + fgSizer->Add( m_tltMin, 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgSizer->Add( dash2 , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgSizer->Add( m_tltMax, 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgSizer->AddStretchSpacer(); + + fgSizer->Add( m_chkSg , 0, wxALL|wxALIGN_CENTER_VERTICAL , 5 ); + fgSizer->Add( m_sgMin , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgSizer->Add( dash3 , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgSizer->Add( m_sgMax , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgSizer->Add( m_laue , 0, wxALL, 5 ); + + //final assembly of parts + bSizer->Add( fgSizer, 0, wxALIGN_CENTER_HORIZONTAL, 5 ); + // bSizer->Add( m_prdTbl, 1, wxSHAPED|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_HORIZONTAL ); + bSizer->Add( m_prdTbl, 1, wxEXPAND ); + this->SetMinSize(bSizer->GetMinSize()); + this->SetSizer( bSizer ); + this->Layout(); + + m_laue->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( MasterFileFilterPanel::ChoiceChanged ), NULL, this ); +} + +MasterFileFilterPanel::~MasterFileFilterPanel() { + m_laue->Disconnect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( MasterFileFilterPanel::ChoiceChanged ), NULL, this ); +} + +#endif//_MP_FILT_PAN_H_ diff --git a/include/wx/MasterFileList.hpp b/include/wx/MasterFileList.hpp new file mode 100644 index 0000000..bacfcc6 --- /dev/null +++ b/include/wx/MasterFileList.hpp @@ -0,0 +1,572 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _MASTERFILELIST_H_ +#define _MASTERFILELIST_H_ + +#include + +#include "PeriodicTablePanel.h" + +//@brief : get the address of the function pointer stored by a std::function +//@param f: function to get address of +//@return : function address +template +size_t getAddress(std::function f) { + typedef T(fnType)(U...); + fnType ** fnPointer = f.template target(); + return (size_t) *fnPointer; +} + +//@brief: class to hold information about a master pattern file +struct MasterFile { + static const wxString SgNames[230];//could do this with hm.hpp but this is easier and probably faster + + //actual data + wxString pth;//full path + float kv ;//voltage + float tlt;//tilt (degrees) + ElementMask els;//elements + int sg ;//space group number + + //string versions of data + wxString dir;//directory component of path + wxString fil;//file component of path + wxString sEl;//element + + std::bitset<4> flg;//flags for external use (I'll use them to mark the filtered and checked status) + + //@brief: comparison op + bool operator< (const MasterFile& rhs) const {return pth < rhs.pth;} + bool operator==(const MasterFile& rhs) const {return pth == rhs.pth;} + + //@brief: attempt to read the file currently stored in pth + //@return: true if successful + bool read(); + + //@brief: convert various elements to strings nicely + wxString system() const; + wxString sgName() const {return sg < 1 || sg > 230 ? _("?") : SgNames[sg-1];} + + //@brief: constructor takes file path + MasterFile(wxString path = "") : pth(path), dir(wxFileName(pth).GetPath()), fil(wxFileName(pth).GetName()) {} + + //@brief: helper functions to sort a MasterFile by various fields + //@note : most sort functions use pth as tiebreak + typedef std::function CompFunc; + static bool SameComp(const CompFunc lhs, const CompFunc rhs) {return getAddress(lhs) == getAddress(rhs);} + static bool PthAsc (const MasterFile& lhs, const MasterFile& rhs) {return lhs.pth < rhs.pth;}//operator< + static bool PthDes (const MasterFile& lhs, const MasterFile& rhs) {return lhs.pth > rhs.pth;} + static bool FilAsc (const MasterFile& lhs, const MasterFile& rhs) {return lhs.fil == rhs.fil ? lhs.pth < rhs.pth : lhs.fil < rhs.fil;} + static bool FilDes (const MasterFile& lhs, const MasterFile& rhs) {return lhs.fil == rhs.fil ? lhs.pth > rhs.pth : lhs.fil > rhs.fil;} + static bool KvAsc (const MasterFile& lhs, const MasterFile& rhs) {return lhs.kv == rhs.kv ? lhs.pth < rhs.pth : lhs.kv < rhs.kv ;} + static bool KvDes (const MasterFile& lhs, const MasterFile& rhs) {return lhs.kv == rhs.kv ? lhs.pth > rhs.pth : lhs.kv > rhs.kv ;} + static bool TltAsc (const MasterFile& lhs, const MasterFile& rhs) {return lhs.tlt == rhs.tlt ? lhs.pth < rhs.pth : lhs.tlt < rhs.tlt;} + static bool TltDes (const MasterFile& lhs, const MasterFile& rhs) {return lhs.tlt == rhs.tlt ? lhs.pth > rhs.pth : lhs.tlt > rhs.tlt;} + static bool ElsAsc (const MasterFile& lhs, const MasterFile& rhs) {return lhs.sEl == rhs.sEl ? lhs.pth < rhs.pth : lhs.sEl < rhs.sEl;} + static bool ElsDes (const MasterFile& lhs, const MasterFile& rhs) {return lhs.sEl == rhs.sEl ? lhs.pth > rhs.pth : lhs.sEl > rhs.sEl;} + static bool SgAsc (const MasterFile& lhs, const MasterFile& rhs) {return lhs.sg == rhs.sg ? lhs.pth < rhs.pth : lhs.sg < rhs.sg ;} + static bool SgDes (const MasterFile& lhs, const MasterFile& rhs) {return lhs.sg == rhs.sg ? lhs.pth > rhs.pth : lhs.sg > rhs.sg ;} + static bool SgNmAsc(const MasterFile& lhs, const MasterFile& rhs) {return (lhs.sg > 0 && lhs.sg < 231 && rhs.sg > 0 && rhs.sg < 231 && lhs.sg == rhs.sg) ? lhs.pth < rhs.pth : SgNames[lhs.sg-1] < SgNames[rhs.sg-1];} + static bool SgNmDes(const MasterFile& lhs, const MasterFile& rhs) {return (lhs.sg > 0 && lhs.sg < 231 && rhs.sg > 0 && rhs.sg < 231 && lhs.sg == rhs.sg) ? lhs.pth > rhs.pth : SgNames[lhs.sg-1] > SgNames[rhs.sg-1];} + static bool SysAsc (const MasterFile& lhs, const MasterFile& rhs) {return lhs.system() == rhs.system() ? ( lhs.sg == rhs.sg ? lhs.pth < rhs.pth : lhs.sg < rhs.sg ) : lhs.system() < rhs.system();} + static bool SysDes (const MasterFile& lhs, const MasterFile& rhs) {return lhs.system() == rhs.system() ? ( lhs.sg == rhs.sg ? lhs.pth > rhs.pth : lhs.sg > rhs.sg ) : lhs.system() > rhs.system();} + + //@brief : set the filter flag using bounds + //@param fKv : upper and lower bounds for kv filtering + //@param fTlt: upper and lower bounds for tilt filtering + //@param fEl : bitmask of ~TODO should all or any elements be required? + //@param fSg : upper and lower bounds for space group filtering + void filter(std::pair fKv, std::pair fTlt, ElementMask fEl, std::pair fSg) { + flg.set(2, kv >= fKv .first && kv <= fKv .second && + tlt >= fTlt.first && tlt <= fTlt.second && + (fEl.none() ? true : (fEl & els) == fEl)&& + sg >= fSg .first && sg <= fSg .second); + } + + //@brief : set the search flag using search string + //@param str: search string + void search(wxString str) {flg.set(3, wxNOT_FOUND != pth.Lower().Find(str) );} + // void search(wxString str) {flg.set(3, wxNOT_FOUND != pth.Lower().Find(str) || wxNOT_FOUND != sgName().Lower().Find(str) );} + + //@brief : check if this item has passed the filter and search tests + //@return: true if both passed, false otherwise + bool display() const {return flg.test(2) && flg.test(3);} + + //@brief : (un)check this item + //@param chk: check state + void check(bool chk) {flg.set(1, chk);} + + //@brief : get check state + //@return: check state + bool isChecked() const {return flg.test(1);} + + //@brief : check if the file has been parsed + //@return: true if read() has previously returned true + bool isRead() const {return flg.test(0);} +}; + +//@brief: gui element to display a a list of master files +class MasterFileList: public wxListCtrl{ + std::vector m_allFiles;//list of all files (m_srtFnc exists) + MasterFile::CompFunc m_srtFnc ;//function to sort file list with (or nullptr for unsorted) + std::vector< std::vector::iterator > m_disFiles;//files to display (filtered) + + std::pair m_kvFilt ;//kv filtering range + std::pair m_tltFilt ;//tilt filtering range + ElementMask m_elFlt ;//element filtering + std::pair m_sgFilt ;//space group filtering + + wxString m_strSrch ;//current search string + + void populateDis();//@brief: build list of visible items + + void ColClicked (wxListEvent& Event);//sort on column click + void ItemChecked (wxListEvent& Event) {m_disFiles[Event.GetIndex()]->check(true ); RefreshItem(Event.GetIndex());}// Event.Skip();}//track check/uncheck state + void ItemUnChecked(wxListEvent& Event) {m_disFiles[Event.GetIndex()]->check(false); RefreshItem(Event.GetIndex());}// Event.Skip();}//track check/uncheck state + + public: + //@brief : get the indices of currently selected items + //@return: indices + std::vector GetSelection(); + + //@brief : set the sort function + update + //@param func: new sort function (or nullptr to stop sorting) + void setSort(MasterFile::CompFunc func);//set the sort function + + static std::pair AllKv () {return std::pair(0.0f , 1.0e20 );} + static std::pair AllTlt() {return std::pair(-400.0f, 400.0f );} + static ElementMask AllEl () {return ElementMask() ;} + static std::pair AllSg () {return std::pair(1 , 230 );} + + //@brief : set filter bounds + //@param kv : upper and lower bounds for kv filtering + //@param tlt: upper and lower bounds for tilt filtering + //@param el : bitmask of ~TODO should all or any elements be required? + //@param sg : upper and lower bounds for space group filtering + void setFilters(std::pair kv = AllKv(), std::pair tlt = AllTlt(), ElementMask el = AllEl(), std::pair sg = AllSg()); + + //@brief : get filter bounds + //@param kv : location to write upper and lower bounds for kv filtering + //@param tlt: location to write upper and lower bounds for tilt filtering + //@param el : location to write bitmask of ~TODO should all or any elements be required? + //@param sg : location to write upper and lower bounds for space group filtering + void getFilters(std::pair& kv, std::pair& tlt, ElementMask& el, std::pair& sg) const; + + //@brief : set the string search term + //@param str: new search term + void setSearch(wxString str); + + //@brief : add a new file to the list + //@param item: file to add + //@param chk : should the item be ticked + //@note : only added if item.isRead() ? true : item.read() + //@return : true if item was added (false otherwise) + bool AddItem(MasterFile item, bool chk); + + //@brief : check if the list has a master file (even if it isn't displayed) + //@param pth: path of file to check for + //@return : true if the item is already included + bool HasItem(wxString pth) {for(const MasterFile& f : m_allFiles) {if(f.pth == pth) return true;} return false;} + + //@brief : remove an item + //@param item: index of item to remove + void RemoveItem(long item); + + MasterFileList(wxWindow* parent, const wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize); + ~MasterFileList(); + + wxString OnGetItemText(long item, long column) const; + bool OnGetItemIsChecked(long item) const {return m_disFiles[item]->isChecked();} + + MasterFile GetItem(const size_t item) {return *m_disFiles[item];} + + //@brief: remove all currently selected items + void RemoveSelected(); + + //@brief : move all currently selected items one position up or down + //@param up: up or down + void MoveSelected(const bool up); + + //@brief : enable multiple item selection + //@param enb: enable/disable + void EnableMultipleSelection(const bool enb = true) {SetSingleStyle (wxLC_SINGLE_SEL, !enb);} + + //@brief : get all files in list + //@return: all files (not just displayed ones) + std::vector getAllFiles() const; + +}; + +//////////////////////////////////////////////////////////////////////// +// MasterFileList // +//////////////////////////////////////////////////////////////////////// + +//@brief: build list of visible items +void MasterFileList::populateDis() { + //rebuild display list + m_disFiles.clear(); + SetItemCount(0); + for(std::vector::iterator iter = m_allFiles.begin(); iter != m_allFiles.end(); ++iter) { + if(iter->display()) { + m_disFiles.push_back(iter); + RefreshItem(m_disFiles.size()-1); + } + } + SetItemCount(m_disFiles.size()); + + for(size_t i = 0; i < m_disFiles.size(); i++) { + CheckItem(i, m_disFiles[i]->isChecked()); + // SetItemState(i, m_disFiles[i]->isChecked(), wxLIST_STATE_SELECTED); + // RefreshItem(i); + } +} + +void MasterFileList::ColClicked(wxListEvent& Event) { + if(m_srtFnc) { + switch(Event.GetColumn()) { + case 0: setSort( MasterFile::SameComp(m_srtFnc, MasterFile::PthAsc ) ? MasterFile::PthDes : MasterFile::PthAsc ); break; + case 1: setSort( MasterFile::SameComp(m_srtFnc, MasterFile::FilAsc ) ? MasterFile::FilDes : MasterFile::FilAsc ); break; + case 2: setSort( MasterFile::SameComp(m_srtFnc, MasterFile::KvAsc ) ? MasterFile::KvDes : MasterFile::KvAsc ); break; + case 3: setSort( MasterFile::SameComp(m_srtFnc, MasterFile::TltAsc ) ? MasterFile::TltDes : MasterFile::TltAsc ); break; + case 4: setSort( MasterFile::SameComp(m_srtFnc, MasterFile::ElsAsc ) ? MasterFile::ElsDes : MasterFile::ElsAsc ); break; + case 5: setSort( MasterFile::SameComp(m_srtFnc, MasterFile::SysAsc ) ? MasterFile::SysDes : MasterFile::SysAsc ); break; + case 6: setSort( MasterFile::SameComp(m_srtFnc, MasterFile::SgAsc ) ? MasterFile::SgDes : MasterFile::SgAsc ); break; + case 7: setSort( MasterFile::SameComp(m_srtFnc, MasterFile::SgNmAsc) ? MasterFile::SgNmDes : MasterFile::SgNmAsc); break; + } + } +} + +//@brief : get the indices of currently selected items +//@return: indices +std::vector MasterFileList::GetSelection() { + //build list of selected indices + long idx = -1; + std::vector sel; + while ((idx = GetNextItem(idx, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != wxNOT_FOUND) sel.push_back(idx); + return sel; +} + +//@brief : set the sort function + update +//@param func: new sort function (or nullptr to stop sorting) +void MasterFileList::setSort(MasterFile::CompFunc func) { + bool same = func && m_srtFnc ? MasterFile::SameComp(func, m_srtFnc) : bool(func) == bool(m_srtFnc); + if(same) return; + m_srtFnc = func; + if(m_srtFnc) { + std::sort(m_allFiles.begin(), m_allFiles.end(), m_srtFnc); + populateDis(); + } +} + +void MasterFileList::setFilters(std::pair kv, std::pair tlt, ElementMask el, std::pair sg) { + if(kv != m_kvFilt || tlt != m_tltFilt || el != m_elFlt || sg != m_sgFilt ) {//filters actually changed + //save filters and clear filtered list + m_kvFilt = kv ; + m_tltFilt = tlt; + m_elFlt = el ; + m_sgFilt = sg ; + + //loop over all files applying new filters + for(std::vector::iterator iter = m_allFiles.begin(); iter != m_allFiles.end(); ++iter) iter->filter(m_kvFilt, m_tltFilt, m_elFlt, m_sgFilt); + populateDis(); + } +} + +//@brief : get filter bounds +//@param kv : location to write upper and lower bounds for kv filtering +//@param tlt: location to write upper and lower bounds for tilt filtering +//@param el : location to write bitmask of ~TODO should all or any elements be required? +//@param sg : location to write upper and lower bounds for space group filtering +void MasterFileList::getFilters(std::pair& kv, std::pair& tlt, ElementMask& el, std::pair& sg) const { + kv = m_kvFilt ; + tlt = m_tltFilt; + el = m_elFlt ; + sg = m_sgFilt ; +} + +//@brief : set the string search term +//@param str: new search term +void MasterFileList::setSearch(wxString str) { + if(str.Lower() != m_strSrch) { + m_strSrch = str.Lower();//case insensitive + + //loop over all files applying new filters + for(std::vector::iterator iter = m_allFiles.begin(); iter != m_allFiles.end(); ++iter) iter->search(m_strSrch); + populateDis(); + } +} + +//@brief : add a new file to the list +//@param item: file to add +//@note : only added if item.read() == true +//@param chk : should the item be ticked +//@return : true if item was added (false otherwise) +bool MasterFileList::AddItem(MasterFile item, bool chk) { + if(!(item.isRead() ? true : item.read())) return false; + if(std::find(m_allFiles.cbegin(), m_allFiles.cend(), item) == m_allFiles.cend()) { + //compute item filtering and add to list + item.filter(m_kvFilt, m_tltFilt, m_elFlt, m_sgFilt); + item.search(m_strSrch); + item.check(chk); + + if(m_srtFnc) { + std::vector::iterator iter = std::upper_bound(m_allFiles.begin(), m_allFiles.end(), item, m_srtFnc);//find sorted insert location + m_allFiles.insert(iter, item);//insert item into list + } else { + m_allFiles.push_back(item); + } + populateDis();//rebuild display list + return true; + } + return false; +} + +//@brief : remove an item +//@param item: index of item to remove +void MasterFileList::RemoveItem(long item) { + m_allFiles.erase(m_disFiles[item]); + populateDis(); +} + +//Constructor, sets up virtual report list with 3 columns +MasterFileList::MasterFileList(wxWindow* parent, const wxWindowID id, const wxPoint& pos, const wxSize& size): + wxListCtrl(parent, id, pos, size, wxLC_REPORT|wxLC_VIRTUAL|wxLC_HRULES|wxLC_SINGLE_SEL), + m_srtFnc(MasterFile::PthAsc) +{ + // add columns + AppendColumn("Folder" , wxLIST_FORMAT_LEFT, 70); + AppendColumn("File" , wxLIST_FORMAT_LEFT, 140); + AppendColumn("kV" , wxLIST_FORMAT_LEFT, 35); + AppendColumn("Tilt" , wxLIST_FORMAT_LEFT, 35); + AppendColumn("Els" , wxLIST_FORMAT_LEFT, 45); + AppendColumn("Laue" , wxLIST_FORMAT_LEFT, 55); + AppendColumn("SG #" , wxLIST_FORMAT_LEFT, 40); + // AppendColumn("SG Name" , wxLIST_FORMAT_LEFT, 70); + EnableCheckBoxes (true);//used to be EnableCheckboxes + SetItemCount(0); + setFilters();//default filters + EnableAlternateRowColours(); + + // Connect Events + this->Connect( wxEVT_COMMAND_LIST_COL_CLICK, wxListEventHandler( MasterFileList::ColClicked ), NULL, this ); + this->Connect( wxEVT_LIST_ITEM_UNCHECKED, wxListEventHandler( MasterFileList::ItemUnChecked ), NULL, this ); + this->Connect( wxEVT_LIST_ITEM_CHECKED, wxListEventHandler( MasterFileList::ItemChecked ), NULL, this ); +} + +MasterFileList::~MasterFileList() +{ + // Disconnect Events + this->Disconnect( wxEVT_COMMAND_LIST_COL_CLICK, wxListEventHandler( MasterFileList::ColClicked ), NULL, this ); + this->Disconnect( wxEVT_LIST_ITEM_UNCHECKED, wxListEventHandler( MasterFileList::ItemUnChecked ), NULL, this ); + this->Disconnect( wxEVT_LIST_ITEM_CHECKED, wxListEventHandler( MasterFileList::ItemChecked ), NULL, this ); +} + +//Overload virtual method of wxListCtrl to provide text data for virtual list +wxString MasterFileList::OnGetItemText(long item, long column) const { + const std::vector::iterator& mf = m_disFiles[item]; + switch(column) { + case 0: return mf->dir; + case 1: return mf->fil; + case 2: return wxString::Format(wxT("%.1f"), mf->kv ); + case 3: return wxString::Format(wxT("%.1f"), mf->tlt); + case 4: return mf->sEl; + case 5: return mf->system(); + case 6: return wxString::Format(wxT("%i"), mf->sg); + case 7: return mf->sgName(); + default: return _("?"); + } + return _("?"); +} + +//@brief: remove all currently selected items +void MasterFileList::RemoveSelected() { + //build list of selected indices and sort do we don't invalidate + std::vector sel = GetSelection(); + std::sort(sel.begin(), sel.end(), [this](const long& lhs, const long& rhs) {return std::distance(this->m_allFiles.begin(), this->m_disFiles[lhs]) < std::distance(this->m_allFiles.begin(), this->m_disFiles[rhs]);}); + + //remove back to front to prevent invalidating unprocessed items and update list + for(std::vector::reverse_iterator iter = sel.rbegin(); iter != sel.rend(); ++iter) m_allFiles.erase(m_disFiles[*iter]); + populateDis(); +} + +//@brief : move all currently selected items one position up or down +//@param up: up or down +void MasterFileList::MoveSelected(const bool up) { + //build list of selected indices + std::vector sel = GetSelection(); + if(!up) std::reverse(sel.begin(), sel.end());//order depends on movement direction + bool haveMoved = false;//have we moved at least one item up + for(size_t i = 0; i < sel.size(); i++) { + bool move; + if(up) + move = sel[i] > 0;//don't move up if we're already at the top + else + move = m_disFiles.size() != sel[i] + 1;//don't move up if we're already at the top + + if(i > 0) move = haveMoved;//make sure there is space above this to move + if(move) { + haveMoved = true; + SetItemState(sel[i], 0, wxLIST_STATE_SELECTED); + if(up) { + SetItemState(sel[i]-1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + std::swap(m_disFiles[sel[i]-1], m_disFiles[sel[i]]); + } else { + SetItemState(sel[i]+1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + std::swap(m_disFiles[sel[i]+1], m_disFiles[sel[i]]); + } + } + } + for(size_t i = 0; i < m_disFiles.size(); i++) RefreshItem(i); +} + +//@brief : get all files in list +//@return: all files (not just displayed ones) +std::vector MasterFileList::getAllFiles() const { + std::vector files; + for(const MasterFile& mf : m_allFiles) files.push_back(mf.pth); + return files; +} + +//////////////////////////////////////////////////////////////////////// +// MasterFileList // +//////////////////////////////////////////////////////////////////////// + +const wxString MasterFile::SgNames[230] = {//could do this with hm.hpp but this is easier and probably faster + _("P 1" ), _("P -1" ), _("P 1 2 1" ), _("P 1 21 1" ), _("C 1 2 1" ), _("P 1 m 1" ), _("P 1 c 1" ), _("C 1 m 1" ), + _("C 1 c 1" ), _("P 1 2/m 1" ), _("P 1 21/m 1"), _("C 1 2/m 1" ), _("P 1 2/c 1" ), _("P 1 21/c 1"), _("C 1 2/c 1" ), _("P 2 2 2" ), + _("P 2 2 21" ), _("P 21 21 2" ), _("P 21 21 21"), _("C 2 2 21" ), _("C 2 2 2" ), _("F 2 2 2" ), _("I 2 2 2" ), _("I 21 21 21"), + _("P m m 2" ), _("P m c 21" ), _("P c c 2" ), _("P m a 2" ), _("P c a 21" ), _("P n c 2" ), _("P m n 21" ), _("P b a 2" ), + _("P n a 21" ), _("P n n 2" ), _("C m m 2" ), _("C m c 21" ), _("C c c 2" ), _("A m m 2" ), _("A e m 2" ), _("A m a 2" ), + _("A e a 2" ), _("F m m 2" ), _("F d d 2" ), _("I m m 2" ), _("I b a 2" ), _("I m a 2" ), _("P m m m" ), _("P n n n" ), + _("P c c m" ), _("P b a n" ), _("P m m a" ), _("P n n a" ), _("P m n a" ), _("P c c a" ), _("P b a m" ), _("P c c n" ), + _("P b c m" ), _("P n n m" ), _("P m m n" ), _("P b c n" ), _("P b c a" ), _("P n m a" ), _("C m c m" ), _("C m c e" ), + _("C m m m" ), _("C c c m" ), _("C m m e" ), _("C c c e" ), _("F m m m" ), _("F d d d" ), _("I m m m" ), _("I b a m" ), + _("I b c a" ), _("I m m a" ), _("P 4" ), _("P 41" ), _("P 42" ), _("P 43" ), _("I 4" ), _("I 41" ), + _("P -4" ), _("I -4" ), _("P 4/m" ), _("P 42/m" ), _("P 4/n" ), _("P 42/n" ), _("I 4/m" ), _("I 41/a" ), + _("P 4 2 2" ), _("P 4 21 2" ), _("P 41 2 2" ), _("P 41 21 2" ), _("P 42 2 2" ), _("P 42 21 2" ), _("P 43 2 2" ), _("P 43 21 2" ), + _("I 4 2 2" ), _("I 41 2 2" ), _("P 4 m m" ), _("P 4 b m" ), _("P 42 c m" ), _("P 42 n m" ), _("P 4 c c" ), _("P 4 n c" ), + _("P 42 m c" ), _("P 42 b c" ), _("I 4 m m" ), _("I 4 c m" ), _("I 41 m d" ), _("I 41 c d" ), _("P -4 2 m" ), _("P -4 2 c" ), + _("P -4 21 m" ), _("P -4 21 c" ), _("P -4 m 2" ), _("P -4 c 2" ), _("P -4 b 2" ), _("P -4 n 2" ), _("I -4 m 2" ), _("I -4 c 2" ), + _("I -4 2 m" ), _("I -4 2 d" ), _("P 4/m m m" ), _("P 4/m c c" ), _("P 4/n b m" ), _("P 4/n n c" ), _("P 4/m b m" ), _("P 4/m n c" ), + _("P 4/n m m" ), _("P 4/n c c" ), _("P 42/m m c"), _("P 42/m c m"), _("P 42/n b c"), _("P 42/n n m"), _("P 42/m b c"), _("P 42/m n m"), + _("P 42/n m c"), _("P 42/n c m"), _("I 4/m m m" ), _("I 4/m c m" ), _("I 41/a m d"), _("I 41/a c d"), _("P 3" ), _("P 31" ), + _("P 32" ), _("R 3" ), _("P -3" ), _("R -3" ), _("P 3 1 2" ), _("P 3 2 1" ), _("P 31 1 2" ), _("P 31 2 1" ), + _("P 32 1 2" ), _("P 32 2 1" ), _("R 3 2" ), _("P 3 m 1" ), _("P 3 1 m" ), _("P 3 c 1" ), _("P 3 1 c" ), _("R 3 m" ), + _("R 3 c" ), _("P -3 1 m" ), _("P -3 1 c" ), _("P -3 m 1" ), _("P -3 c 1" ), _("R -3 m" ), _("R -3 c" ), _("P 6" ), + _("P 61" ), _("P 65" ), _("P 62" ), _("P 64" ), _("P 63" ), _("P -6" ), _("P 6/m" ), _("P 63/m" ), + _("P 6 2 2" ), _("P 61 2 2" ), _("P 65 2 2" ), _("P 62 2 2" ), _("P 64 2 2" ), _("P 63 2 2" ), _("P 6 m m" ), _("P 6 c c" ), + _("P 63 c m" ), _("P 63 m c" ), _("P -6 m 2" ), _("P -6 c 2" ), _("P -6 2 m" ), _("P -6 2 c" ), _("P 6/m m m" ), _("P 6/m c c" ), + _("P 63/m c m"), _("P 63/m m c"), _("P 2 3" ), _("F 2 3" ), _("I 2 3" ), _("P 21 3" ), _("I 21 3" ), _("P m 3" ), + _("P n 3" ), _("F m 3" ), _("F d 3" ), _("I m 3" ), _("P a 3" ), _("I a 3" ), _("P 4 3 2" ), _("P 42 3 2" ), + _("F 4 3 2" ), _("F 41 3 2" ), _("I 4 3 2" ), _("P 43 3 2" ), _("P 41 3 2" ), _("I 41 3 2" ), _("P -4 3 m" ), _("F -4 3 m" ), + _("I -4 3 m" ), _("P -4 3 n" ), _("F -4 3 c" ), _("I -4 3 d" ), _("P m 3 m" ), _("P n 3 n" ), _("P m 3 n" ), _("P n 3 m" ), + _("F m 3 m" ), _("F m 3 c" ), _("F d 3 m" ), _("F d 3 c" ), _("I m 3 m" ), _("I a 3 d" ), +}; + +wxString MasterFile::system() const { + if (sg < 1 ) return _("?" ); + else if(sg < 3 ) return _("-1" ); + else if(sg < 16 ) return _("2/m" ); + else if(sg < 75 ) return _("mmm" ); + else if(sg < 89 ) return _("4/m" ); + else if(sg < 143) return _("4/mmm"); + else if(sg < 149) return _("-3" ); + else if(sg < 168) return _("3m" ); + else if(sg < 177) return _("6/m" ); + else if(sg < 195) return _("6/mmm"); + else if(sg < 207) return _("m-3" ); + else if(sg < 231) return _("m-3m" ); + // else if(sg < 3 ) return _("Tric");//"Triclinic" + // else if(sg < 16 ) return _("Mono");//"Monoclinic" + // else if(sg < 75 ) return _("Orth");//"Orthorhombic" + // else if(sg < 143) return _("Tet" );//"Tetragonal" + // else if(sg < 168) return _("Trig");//"Trigonal" + // else if(sg < 195) return _("Hex" );//"Hexagonal" + // else if(sg < 231) return _("Cub" );//"Cubic" + else return _("?"); +} + +#include "sht_file.hpp" +#include + +//@brief: attempt to read the file currently stored in pth +//@return: true if successful +bool MasterFile::read() { + kv = 0; + tlt = 0; + els = ElementMask(); + sg = 0; + flg.set(0, false); + + if(wxFileExists(pth)) {//first check if file exists + if("sht" == wxFileName(pth).GetExt().Lower()) {//make sure the file extension is correct + try { + //read file and make sure it is an EBSD master pattern spectra + sht::File file; + std::ifstream is(pth.ToStdString(), std::ios::in | std::ios::binary); + file.read(is); + if(sht::Modality::EBSD != file.header.modality()) return false;//some other type of master pattern + + //save meta data + kv = file.header.beamEnergy(); + tlt = file.header.primaryAngle(); + sg = file.material.sgEff(); + + //build up bitmask of elements + for(const sht::CrystalData& xtal : file.material.xtals) { + for(const sht::AtomData& at : xtal.atoms) { + size_t z = at.atZ(); + if(z > 0 && z < 119) { + els.set(z, true); + } else { + return false; + } + } + } + + //convert element bitmask to string and return + sEl = els.str(); + flg.set(0, true); + return true;//read everything + } catch (...) { + return false; + } + } + } + return false; +} + +#endif//_MASTERFILELIST_H_ + diff --git a/include/wx/MasterPatternSelectPanel.h b/include/wx/MasterPatternSelectPanel.h new file mode 100644 index 0000000..3b782cd --- /dev/null +++ b/include/wx/MasterPatternSelectPanel.h @@ -0,0 +1,331 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _MP_SEL_H_ +#define _MP_SEL_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wx/ValidityPanel.h" +#include "wx/MasterFileList.hpp" + +/////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +/// Class MasterPatternSelectPanel +/////////////////////////////////////////////////////////////////////////////// +class MasterPatternSelectPanel : public ValidityPanel { + + MasterFileList * m_selLst ; + wxBitmapButton * m_btnUp ; + wxBitmapButton * m_btnDn ; + wxBitmapButton * m_btnAdd ; + wxBitmapButton * m_btnDel ; + wxBitmapButton * m_btnFlt ; + wxBitmapButton * m_btnDir ; + wxSearchCtrl * m_mpSearch; + MasterFileList * m_libLst ; + + wxArrayString m_hidNames;//hidden patterns from known master pattern list (not matching current sort) + + void UpClicked ( wxCommandEvent& event ) { m_selLst->MoveSelected(true );}//move the selected item in the top box up + void DownClicked ( wxCommandEvent& event ) { m_selLst->MoveSelected(false);}//move the selected item in the top box down + void DelClicked ( wxCommandEvent& event ) { m_libLst->RemoveSelected(); }//remove all selected items in the bottom box + void FltClicked ( wxCommandEvent& event );//update filters + void SearchChanged ( wxCommandEvent& event ) { m_libLst->setSearch(event.GetString()); } + void IdxUnchecked ( wxListEvent & event );//move top box items to bottom box on untick + void KnownChecked ( wxListEvent & event );//move bottom box items to top box on tick + void BrowseClicked ( wxCommandEvent& event );//search for new master pattern files + void DirClicked ( wxCommandEvent& event );//add a folder of files to the bottom box + + protected: + + void ClearSearch() {m_mpSearch->SetValue("");} + + //@brief : add a pattern file to the control + //@param str: pattern file to add + //@param top: add to top box (use) instead of bottom (library) + //@return : status string + std::string AddPatternFile(wxString str, bool top = false); + + public: + + //@brief : check if the panel is currently valid + //@return: "" if valid, reason invalid otherwise + std::string validMsg() const; + + //@brief : get a list of all known master pattern files + //@return: full paths to known pattern files + std::vector getLibrary() const; + + //@brief : set list of known master pattern files + //@param lib: full paths to known pattern files + void setLibrary(const std::vector& lib) {for(const wxString& str : lib) AddPatternFile(str);} + + std::vector getSelected() const; + + MasterPatternSelectPanel( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 300,300 ), long style = wxTAB_TRAVERSAL, const wxString& name = wxEmptyString ); + ~MasterPatternSelectPanel(); +}; + +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version Oct 26 2018) +// http://www.wxformbuilder.org/ +// +// PLEASE DO *NOT* EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// + +#include "SinglePanelDialog.h" +#include "MasterFileFilterPanel.h" + +void MasterPatternSelectPanel::FltClicked( wxCommandEvent& event ) { + //start by getting existings filters + std::pair kv, tlt; + std::pair sg; + ElementMask el; + m_libLst->getFilters(kv, tlt, el, sg); + if(MasterFileList::AllKv () == kv ) {kv .first = kv .second = NAN;} + if(MasterFileList::AllTlt() == tlt) {tlt.first = tlt.second = NAN;} + SinglePanelDialog dlg(this, wxID_ANY, "Select Filters"); + dlg.getPanel()->setBounds(kv, tlt, el, sg); + if(wxID_OK == dlg.ShowModal()) { + dlg.getPanel()->getBounds(kv, tlt, el, sg); + if(kv .first != kv .first) kv = MasterFileList::AllKv (); + if(tlt.first != tlt.first) tlt = MasterFileList::AllTlt(); + m_libLst->setFilters(kv, tlt, el, sg); + } +} + +//move top box items to bottom box on untick +void MasterPatternSelectPanel::IdxUnchecked( wxListEvent& event ) { + m_libLst->AddItem(m_selLst->GetItem(event.GetIndex()), false); + m_selLst->RemoveItem(event.GetIndex()); + testValid(); +} + +//move bottom box items to top box on tick +void MasterPatternSelectPanel::KnownChecked( wxListEvent& event ) { + if(wxEVT_LIST_ITEM_ACTIVATED == event.GetEventType()) { + std::vector sel = m_libLst->GetSelection(); + for(const long& i : sel) m_selLst->AddItem(m_libLst->GetItem(i), true ); + m_libLst->RemoveSelected(); + } else {//check box event + m_selLst->AddItem(m_libLst->GetItem(event.GetIndex()), true ); + m_libLst->RemoveItem(event.GetIndex()); + } + testValid(); +} + +//@brief: search for new master pattern files +void MasterPatternSelectPanel::BrowseClicked ( wxCommandEvent& event ) { + //browse for file + wxFileDialog openFileDlg(this, _("Add Master Pattern File"), wxEmptyString, wxEmptyString, "SHT files (*.sht)|*.sht", wxFD_OPEN|wxFD_FILE_MUST_EXIST); + if(openFileDlg.ShowModal() == wxID_CANCEL) return; + + //do the file loading + std::string msg = AddPatternFile(openFileDlg.GetPath(), true); + if(!msg.empty()) { + wxMessageDialog msgDlg(this, msg, "Error Adding File"); + msgDlg.ShowModal(); + } +} + +#include +#include + +//@brief: add a folder of files to the bottom box +void MasterPatternSelectPanel::DirClicked ( wxCommandEvent& event ) { + //browse for folder + wxDirDialog openDirDlg(this, _("Add Master Pattern Folder"), wxEmptyString, wxDD_DIR_MUST_EXIST); + if(openDirDlg.ShowModal() == wxID_CANCEL) return; + + //loop over all files in folder + wxArrayString files; + wxDir::GetAllFiles(openDirDlg.GetPath(), &files, "*.sht", wxDIR_DIRS | wxDIR_FILES); + std::vector added; + for(const wxString& str : files) { + if(AddPatternFile(str, false).empty()) added.push_back(str); + } +} + +//@brief : check if the panel is currently valid +//@return: "" if valid, reason invalid otherwise +std::string MasterPatternSelectPanel::validMsg() const { + size_t numIt = m_selLst->GetItemCount(); + if(0 == numIt) return "at least one master pattern required (double click/enter to move)"; + else if (1 == numIt) return ""; + MasterFile mf0 = m_selLst->GetItem(0); + for(size_t i = 1; i < numIt; i++) { + MasterFile mfi = m_selLst->GetItem(i); + if(mfi.kv != mf0.kv ) return "all master patterns must have the same kv"; + if(mfi.tlt != mf0.tlt) return "all master patterns must have the same tilt"; + } + return ""; +} + +//@brief : get a list of all known master pattern files +//@return: full paths to known pattern files +std::vector MasterPatternSelectPanel::getLibrary() const { + std::vector sel = m_selLst->getAllFiles(); + std::vector knw = m_libLst->getAllFiles(); + knw.insert(knw.end(), sel.begin(), sel.end()); + return knw; +} + +std::vector MasterPatternSelectPanel::getSelected() const { + std::vector sel = m_selLst->getAllFiles(); + std::vector ret; + for(const wxString& str : sel) ret.push_back(str.ToStdString()); + return ret; +} + +MasterPatternSelectPanel::MasterPatternSelectPanel( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name ) : ValidityPanel( parent, id, pos, size, style, name ) { + //split panel into 2 vertical boxes + wxBoxSizer * bMP = new wxBoxSizer ( wxVERTICAL ); + wxStaticBoxSizer* sbMP = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, wxT("Indexing Master Patterns") ), wxVERTICAL ); + wxStaticBoxSizer* sbLibMP = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, wxT("Master Pattern Library" ) ), wxVERTICAL ); + bMP->Add( sbMP , 1, wxEXPAND, 5 ); + bMP->Add( sbLibMP, 1, wxEXPAND, 5 ); + + //build sizer for strip of bottons to each panel + wxBoxSizer * hSizerMP = new wxBoxSizer( wxHORIZONTAL ); + wxBoxSizer * hSizerKwn = new wxBoxSizer( wxHORIZONTAL ); + + //elements for top box + m_selLst = new MasterFileList( sbMP ->GetStaticBox() ); + m_btnUp = new wxBitmapButton( sbMP ->GetStaticBox(), wxID_ANY, wxArtProvider::GetBitmap( wxART_GO_UP , wxART_BUTTON ), wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW ); + m_btnDn = new wxBitmapButton( sbMP ->GetStaticBox(), wxID_ANY, wxArtProvider::GetBitmap( wxART_GO_DOWN , wxART_BUTTON ), wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW ); + m_btnAdd = new wxBitmapButton( sbMP ->GetStaticBox(), wxID_ANY, wxArtProvider::GetBitmap( wxART_FILE_OPEN , wxART_BUTTON ), wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW ); + m_mpSearch = new wxSearchCtrl ( sbLibMP->GetStaticBox(), wxID_ANY, wxEmptyString , wxDefaultPosition, wxDefaultSize, 0 ); + m_libLst = new MasterFileList( sbLibMP->GetStaticBox() ); + m_btnDel = new wxBitmapButton( sbLibMP->GetStaticBox(), wxID_ANY, wxArtProvider::GetBitmap( wxART_DELETE , wxART_BUTTON ), wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW ); + m_btnFlt = new wxBitmapButton( sbLibMP->GetStaticBox(), wxID_ANY, wxArtProvider::GetBitmap( wxART_FIND , wxART_BUTTON ), wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW ); + m_btnDir = new wxBitmapButton( sbLibMP->GetStaticBox(), wxID_ANY, wxArtProvider::GetBitmap( wxART_FOLDER_OPEN, wxART_BUTTON ), wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW ); + + m_selLst->setSort(MasterFile::CompFunc()); + #ifndef __WXMAC__ + m_mpSearch->ShowSearchButton( true ); + #endif + m_mpSearch->ShowCancelButton( true ); + m_libLst->EnableMultipleSelection(true); + + //finishin assmebling sizers + sbMP ->Add( m_selLst , 1, wxALL|wxEXPAND, 5 ); + sbMP ->Add( hSizerMP , 0, wxEXPAND, 5 ); + sbLibMP->Add( m_mpSearch, 0, wxALL|wxEXPAND, 5 ); + sbLibMP->Add( m_libLst , 1, wxALL|wxEXPAND, 5 ); + sbLibMP->Add( hSizerKwn , 0, wxEXPAND, 5 ); + + //assemble indexing button strip + hSizerMP ->Add( m_btnUp , 0, wxALIGN_CENTER_VERTICAL|wxLEFT , 5 ); + hSizerMP ->Add( m_btnDn , 0, wxALIGN_CENTER_VERTICAL|wxLEFT , 5 ); + hSizerMP ->Add( 0 , 0, 1, wxEXPAND , 5 ); + hSizerMP ->Add( m_btnAdd, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + + //assemble library button strip + hSizerKwn->Add( m_btnDel, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + hSizerKwn->Add( m_btnFlt, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + hSizerKwn->Add( 0 , 0, 1, wxEXPAND , 5 ); + hSizerKwn->Add( m_btnDir, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + + this->SetSizer( bMP ); + this->Layout(); + + // Connect Events + m_selLst ->Connect( wxEVT_LIST_ITEM_UNCHECKED , wxListEventHandler ( MasterPatternSelectPanel::IdxUnchecked ), NULL, this ); + m_btnUp ->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MasterPatternSelectPanel::UpClicked ), NULL, this ); + m_btnDn ->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MasterPatternSelectPanel::DownClicked ), NULL, this ); + m_btnAdd ->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MasterPatternSelectPanel::BrowseClicked ), NULL, this ); + m_btnDel ->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MasterPatternSelectPanel::DelClicked ), NULL, this ); + m_btnFlt ->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MasterPatternSelectPanel::FltClicked ), NULL, this ); + m_btnDir ->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MasterPatternSelectPanel::DirClicked ), NULL, this ); + m_mpSearch->Connect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( MasterPatternSelectPanel::SearchChanged ), NULL, this ); + m_libLst ->Connect( wxEVT_LIST_ITEM_CHECKED , wxListEventHandler ( MasterPatternSelectPanel::KnownChecked ), NULL, this ); + m_selLst ->Connect( wxEVT_LIST_ITEM_ACTIVATED , wxListEventHandler ( MasterPatternSelectPanel::IdxUnchecked ), NULL, this ); + m_libLst ->Connect( wxEVT_LIST_ITEM_ACTIVATED , wxListEventHandler ( MasterPatternSelectPanel::KnownChecked ), NULL, this ); +} + +MasterPatternSelectPanel::~MasterPatternSelectPanel() { + // Disconnect Events + m_selLst ->Disconnect( wxEVT_LIST_ITEM_UNCHECKED , wxListEventHandler ( MasterPatternSelectPanel::IdxUnchecked ), NULL, this ); + m_btnUp ->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MasterPatternSelectPanel::UpClicked ), NULL, this ); + m_btnDn ->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MasterPatternSelectPanel::DownClicked ), NULL, this ); + m_btnAdd ->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MasterPatternSelectPanel::BrowseClicked ), NULL, this ); + m_btnDel ->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MasterPatternSelectPanel::DelClicked ), NULL, this ); + m_btnFlt ->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MasterPatternSelectPanel::FltClicked ), NULL, this ); + m_btnDir ->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MasterPatternSelectPanel::DirClicked ), NULL, this ); + m_mpSearch->Disconnect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( MasterPatternSelectPanel::SearchChanged ), NULL, this ); + m_libLst ->Disconnect( wxEVT_LIST_ITEM_CHECKED , wxListEventHandler ( MasterPatternSelectPanel::KnownChecked ), NULL, this ); + m_selLst ->Disconnect( wxEVT_LIST_ITEM_ACTIVATED , wxListEventHandler ( MasterPatternSelectPanel::IdxUnchecked ), NULL, this ); + m_libLst ->Disconnect( wxEVT_LIST_ITEM_ACTIVATED , wxListEventHandler ( MasterPatternSelectPanel::KnownChecked ), NULL, this ); +} + +//@brief : add a pattern file to the control +//@param str: pattern file to add +//@param top: add to top box (use) instead of bottom (library) +//@return : status string +std::string MasterPatternSelectPanel::AddPatternFile(wxString str, bool top) { + if(m_selLst->HasItem(str)) return "file already in indexing list"; + if(top) {// + if(m_libLst->HasItem(str)) return "file already in library (please add from library instead)"; + bool added = m_selLst->AddItem(MasterFile(str), true ); + testValid(); + return added ? "" : "couldn't read master pattern from file"; + } else { + if(m_libLst->HasItem(str)) return "file already in library"; + // ClearSearch();//it is probably confusing to users if the new file doesn't show up + return m_libLst->AddItem(MasterFile(str), false) ? "" : "couldn't read master pattern from file"; + } +} + +#endif//_MP_SEL_H_ diff --git a/include/wx/PatternCenterPanel.h b/include/wx/PatternCenterPanel.h new file mode 100644 index 0000000..99b4884 --- /dev/null +++ b/include/wx/PatternCenterPanel.h @@ -0,0 +1,583 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _PAT_CEN_PAN_H_ +#define _PAT_CEN_PAN_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wx/ValidityPanel.h" + +/////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +/// Class PatternCenterPanel +/////////////////////////////////////////////////////////////////////////////// +class PatternCenterPanel : public ValidityPanel +{ + private: + size_t m_lstBin = 1;//last binning value + size_t m_binW, m_binH;//binned detector size (must be set from external knowledge) + + wxIntegerValidator valBin;//bining + wxFloatingPointValidator valDlt;//pixel size + wxFloatingPointValidator valWdt;//detector width + wxFloatingPointValidator valPc ;//pcx/pcy + wxFloatingPointValidator valL ;//L + wxFloatingPointValidator xyStar;//x*/y* + wxFloatingPointValidator zStar ;//z* + wxFloatingPointValidator valTlt;//tilt + + protected: + wxTextCtrl* m_txtBin ; + wxTextCtrl* m_txtUnDetW; + wxTextCtrl* m_txtPxSz ; + wxTextCtrl* m_txtDetW ; + wxChoice * m_chcVendor; + wxTextCtrl* m_txtPcx ; + wxTextCtrl* m_txtXstar ; + wxTextCtrl* m_txtPcy ; + wxTextCtrl* m_txtYstar ; + wxTextCtrl* m_txtL ; + wxTextCtrl* m_txtZstar ; + wxTextCtrl* m_txtTlt ; + wxButton * m_btnFit ; + + //@brief : enable/disable the pattern center part of the panel + //@param enb: enable state + void EnablePatCen(bool enb); + + //@brief: compute the emsoft pattern center from x*, y*, z* if possible + void computeEMsoft(); + + //@brief: compute the vendor pattern center from emsoft pattern center if possible + void computeVendor(); + + // Virtual event handlers, overide them in your derived class + void BinningChanged( wxCommandEvent& event ) { binChged (); testValid();} + void PixSzChanged ( wxCommandEvent& event ) { pixSzChged(); testValid();} + void DetWidChanged ( wxCommandEvent& event ) { detWChged (); testValid();} + void VendorChanged ( wxCommandEvent& event ) { computeVendor(); testValid();} + void PcxChanged ( wxCommandEvent& event ) { computeVendor(); testValid();} + void PcyChanged ( wxCommandEvent& event ) { computeVendor(); testValid();} + void LChanged ( wxCommandEvent& event ) { computeVendor(); testValid();} + void XstarChanged ( wxCommandEvent& event ) { computeEMsoft(); testValid();} + void YstarChanged ( wxCommandEvent& event ) { computeEMsoft(); testValid();} + void ZstarChanged ( wxCommandEvent& event ) { computeEMsoft(); testValid();} + void TltChanged ( wxCommandEvent& event ) { testValid();} + + void DoFit ( wxCommandEvent& event ) { event.Skip(); } + + void binChged (); + void pixSzChged();//update detector size from pixel size + void detWChged ();//update pixel size from pixel size + + //check if text fields are empty + bool hasBin () const {return !m_txtBin ->GetLineText(0).IsEmpty();} + bool hasUbDW() const {return !m_txtUnDetW->GetLineText(0).IsEmpty();} + bool hasPxSz() const {return !m_txtPxSz ->GetLineText(0).IsEmpty();} + bool hasDetW() const {return !m_txtDetW ->GetLineText(0).IsEmpty();} + bool hasPcx () const {return !m_txtPcx ->GetLineText(0).IsEmpty();} + bool hasPcy () const {return !m_txtPcy ->GetLineText(0).IsEmpty();} + bool hasL () const {return !m_txtL ->GetLineText(0).IsEmpty();} + bool hasXst () const {return !m_txtXstar ->GetLineText(0).IsEmpty();} + bool hasYst () const {return !m_txtYstar ->GetLineText(0).IsEmpty();} + bool hasZst () const {return !m_txtZstar ->GetLineText(0).IsEmpty();} + bool hasTlt () const {return !m_txtTlt ->GetLineText(0).IsEmpty();} + + //get numeric values of fields + long getBin () const {long v; m_txtBin ->GetLineText(0).ToLong (&v); return v;} + long getUbDW() const {long v; m_txtUnDetW->GetLineText(0).ToLong (&v); return v;} + double getPxSz() const {double v; m_txtPxSz ->GetLineText(0).ToDouble(&v); return v;} + double getDetW() const {double v; m_txtDetW ->GetLineText(0).ToDouble(&v); return v;} + double getPcx () const {double v; m_txtPcx ->GetLineText(0).ToDouble(&v); return v;} + double getPcy () const {double v; m_txtPcy ->GetLineText(0).ToDouble(&v); return v;} + double getL () const {double v; m_txtL ->GetLineText(0).ToDouble(&v); return v;} + double getXst () const {double v; m_txtXstar ->GetLineText(0).ToDouble(&v); return v;} + double getYst () const {double v; m_txtYstar ->GetLineText(0).ToDouble(&v); return v;} + double getZst () const {double v; m_txtZstar ->GetLineText(0).ToDouble(&v); return v;} + double getTlt () const {double v; m_txtTlt ->GetLineText(0).ToDouble(&v); return v;} + + //set numeric values of fields + void setBin (long v) const {m_txtBin ->Clear(); m_txtBin ->operator<<(v);} + void setUbDW(long v) const {m_txtUnDetW->Clear(); m_txtUnDetW->operator<<(v);} + void setPxSz(double v) const {m_txtPxSz ->Clear(); m_txtPxSz ->operator<<(v);} + void setDetW(double v) const {m_txtDetW ->Clear(); m_txtDetW ->operator<<(v);} + void setPcx (double v) const {m_txtPcx ->Clear(); m_txtPcx ->operator<<(v);} + void setPcy (double v) const {m_txtPcy ->Clear(); m_txtPcy ->operator<<(v);} + void setL (double v) const {m_txtL ->Clear(); m_txtL ->operator<<(v);} + void setXst (double v) const {m_txtXstar ->Clear(); m_txtXstar ->operator<<(v);} + void setYst (double v) const {m_txtYstar ->Clear(); m_txtYstar ->operator<<(v);} + void setZst (double v) const {m_txtZstar ->Clear(); m_txtZstar ->operator<<(v);} + void setTlt (double v) const {m_txtTlt ->Clear(); m_txtTlt ->operator<<(v);} + + public: + //@brief: sanity check the current state + //@return: true if the values parsed from the panel are reasonable, false otherwise + //@note : checks for has a file, has detector sizes, and has an AHE value + std::string validMsg() const { + if(!hasBin()) return "binning empty"; + if(!hasPxSz()) return "binned pixel size empty"; + if(!hasDetW()) return "detector width empty"; + if(!hasPcx()) return "pcx empty"; + if(!hasPcy()) return "pcy empty"; + if(!hasL()) return "L empty"; + if(!hasTlt()) return "detector tilt empty"; + return ""; + } + + PatternCenterPanel( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 400,400 ), long style = wxTAB_TRAVERSAL, const wxString& name = wxEmptyString ); + ~PatternCenterPanel(); + + //@brief : set the binned detector width in pixels (hidden field) + //@param w: binned detector width in pixels + //@param w: binned detector height in pixels + void setBinnedPix(const size_t w, const size_t h) {m_binW = w; m_binH = h; setBin(1);}//should also call same function as BinningChanged + + //@brief : get the pixel size + //@return: pixel size in microns + double getDelta() const {return getPxSz() * getBin();} + + //@brief : get the pixel size + //@param dlt: pixel size in microns + void setDelta(double dlt) {setPxSz(dlt); pixSzChged();} + + //@brief : update the pattern center + //@param x : xstar (or pcx) + //@param y : ystar (or pcy) + //@param z : zstar (or L ) + //@param ven: vendor (must be "EMsoft", "EDAX", "Oxford", or "Bruker") + //@note : if ven == EMsoft the current x/y/z* will be updated, otherwise EMsoft will be updated + void setPatternCenter(const double x, const double y, const double z, std::string ven); + + //@brief: clear pattern center info + void clear(); + + //@brief : get EMsoft pattern center + //@param x: location to write pcx + //@param y: location to write pcy + //@param z: location to write L + void getPatternCenter(double& x, double& y, double& z) {x = getPcx(); y = getPcy(); z = getL();} + + //@brief : set the detector tilt + //@param t: detector tilt in degrees + void setDetTlt(const double t) {setTlt(t);} + + //@brief : get the detector tilt + //@return: detector tilt in degrees + double getDetTlt() const {return getTlt();} + +}; + +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version Oct 26 2018) +// http://www.wxformbuilder.org/ +// +// PLEASE DO *NOT* EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +void PatternCenterPanel::clear() { + EnablePatCen(false); + m_txtBin ->Clear(); + m_txtUnDetW->Clear(); + m_txtPxSz ->Clear(); + m_txtDetW ->Clear(); + m_txtPcx ->Clear(); + m_txtPcy ->Clear(); + m_txtL ->Clear(); + m_txtXstar ->Clear(); + m_txtYstar ->Clear(); + m_txtZstar ->Clear(); + m_txtTlt ->Clear(); + m_chcVendor->SetSelection(0); +} + +//@brief : enable/disable the pattern center part of the panel +//@param enb: enable state +void PatternCenterPanel::EnablePatCen(bool enb) { + m_chcVendor->Enable(enb); + m_txtPcx ->Enable(enb); + m_txtXstar ->Enable(enb); + m_txtPcy ->Enable(enb); + m_txtYstar ->Enable(enb); + m_txtL ->Enable(enb); + m_txtZstar ->Enable(enb); + m_txtTlt ->Enable(enb); + // m_btnFit ->Enable(enb); +} + +//@brief: compute the emsoft pattern center from x*, y*, z* if possible +void PatternCenterPanel::computeEMsoft() { + if(hasXst() && hasYst() && hasZst() && hasPxSz() && hasBin()) { + double pctr[3] = { + getXst(), + getYst(), + getZst(), + }; + double delta = getDelta(); + switch(m_chcVendor->GetSelection()) { + case 0://bruker + pctr[0] = pctr[0] * m_binW - 0.5 * m_binW; + pctr[1] = 0.5 * m_binH - pctr[1] * m_binH; + pctr[2] = pctr[2] * m_binH * delta; + break; + + case 1://edax + pctr[0] = pctr[0] * m_binW - 0.5 * m_binW; + pctr[1] = pctr[1] * m_binW - 0.5 * m_binH; + pctr[2] = pctr[2] * m_binW * delta; + break; + + case 2://oxford + pctr[0] = pctr[0] * m_binW - 0.5 * m_binW; + pctr[1] = pctr[1] * m_binH - 0.5 * m_binH; + pctr[2] = pctr[2] * m_binW * delta; + break; + + default: return;//nothing selected + } + m_txtPcx->ChangeValue(wxString::Format(wxT("%0.3f"), pctr[0])); + m_txtPcy->ChangeValue(wxString::Format(wxT("%0.3f"), pctr[1])); + m_txtL ->ChangeValue(wxString::Format(wxT("%0.2f"), pctr[2])); + } +} + +//@brief: compute the vendor pattern center from emsoft pattern center if possible +void PatternCenterPanel::computeVendor() { + if(hasPcx() && hasPcy() && hasL() && hasPxSz() && hasBin()) { + double pctr[3] = { + getPcx(), + getPcy(), + getL (), + }; + double delta = getDelta(); + switch(m_chcVendor->GetSelection()) { + case 0://bruker + pctr[0] = (pctr[0] + 0.5 * m_binW) / m_binW; + pctr[1] = (0.5 * m_binH - pctr[1]) / m_binH; + pctr[2] = pctr[2] / (delta * m_binH); + break; + + case 1://edax + pctr[0] = (pctr[0] + 0.5 * m_binW) / m_binW; + pctr[1] = (pctr[1] + 0.5 * m_binH) / m_binW; + pctr[2] = pctr[2] / (delta * m_binW); + break; + + case 2://oxford + pctr[0] = (pctr[0] + 0.5 * m_binW) / m_binW; + pctr[1] = (pctr[1] + 0.5 * m_binH) / m_binH; + pctr[2] = pctr[2] / (delta * m_binW); + break; + + default: return;//nothing selected + } + m_txtXstar->ChangeValue(wxString::Format(wxT("%0.6f"), pctr[0])); + m_txtYstar->ChangeValue(wxString::Format(wxT("%0.6f"), pctr[1])); + m_txtZstar->ChangeValue(wxString::Format(wxT("%0.6f"), pctr[2])); + } +} + +void PatternCenterPanel::binChged() { + //compute unbinned size in pixels + if(hasBin()) { + long bin = getBin(); + long unbinPix = bin * m_binW; + m_txtUnDetW->Clear(); + m_txtUnDetW->operator<<(unbinPix); + if(hasPxSz()) detWChged();//if we had a pixel size, update it to keep the detector width fixed + m_lstBin = bin; + } +} + +//update detector size from pixel size +void PatternCenterPanel::pixSzChged() { + if(hasPxSz()) { + EnablePatCen(true); + m_txtDetW->ChangeValue(wxString::Format(wxT("%0.4f"), getPxSz() * (m_binW * getBin()) / 1000));//change value so we dont' issue an event + computeEMsoft(); + } else { + EnablePatCen(false); + } +} + +//update pixel size from pixel size +void PatternCenterPanel::detWChged () { + if(hasDetW()) { + EnablePatCen(true); + m_txtPxSz->ChangeValue(wxString::Format(wxT("%0.2f"), getDetW() * 1000.0 / (m_binW * getBin()))); + computeEMsoft(); + } else { + EnablePatCen(false); + } +} + +//@brief : update the pattern center +//@param x : xstar (or pcx) +//@param y : ystar (or pcy) +//@param z : zstar (or L ) +//@param ven: vendor (must be "EMsoft", "EDAX", "Oxford", or "Bruker") +//@note : if ven == EMsoft the current x/y/z* will be updated, otherwise EMsoft will be updated +void PatternCenterPanel::setPatternCenter(const double x, const double y, const double z, std::string ven) { + std::transform(ven.begin(), ven.end(), ven.begin(), [](unsigned char c){ return std::tolower(c); }); + bool vendor = true; + clear(); + if("bruker" == ven) { + m_chcVendor->SetSelection(0); + } else if("edax" == ven) { + m_chcVendor->SetSelection(1); + } else if("oxford" == ven) { + m_chcVendor->SetSelection(2); + } else if("emsoft" == ven) { + m_txtPcx->ChangeValue(wxString::Format(wxT("%0.3f"), x)); + m_txtPcy->ChangeValue(wxString::Format(wxT("%0.3f"), y)); + m_txtL ->ChangeValue(wxString::Format(wxT("%0.2f"), z)); + vendor = false; + computeVendor(); + } else { + throw std::runtime_error("unknown vendor " + ven); + } + if(vendor) { + m_txtXstar->ChangeValue(wxString::Format(wxT("%0.6f"), x)); + m_txtYstar->ChangeValue(wxString::Format(wxT("%0.6f"), y)); + m_txtZstar->ChangeValue(wxString::Format(wxT("%0.6f"), z)); + computeEMsoft(); + } +} + +/////////////////////////////////////////////////////////////////////////// + +PatternCenterPanel::PatternCenterPanel( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name ) : ValidityPanel( parent, id, pos, size, style, name ) { + //split panel into 2 vertical boxes + wxBoxSizer * bPatCent = new wxBoxSizer( wxVERTICAL ); + wxStaticBoxSizer* sbPixSize = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, wxT("Pixel Size" ) ), wxVERTICAL ); + wxStaticBoxSizer* sbPatCen = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, wxT("Pattern Center") ), wxVERTICAL ); + bPatCent->Add( sbPixSize, 3, wxEXPAND, 5 ); + bPatCent->Add( sbPatCen , 4, wxEXPAND, 5 ); + + //top box is 3x4 grid + wxFlexGridSizer* fgPixSz = new wxFlexGridSizer( 4, 3, 0, 0 ); + fgPixSz->AddGrowableCol( 0 ); fgPixSz->AddGrowableCol( 1 ); fgPixSz->AddGrowableCol( 2 ); + fgPixSz->AddGrowableRow( 0 ); fgPixSz->AddGrowableRow( 1 ); fgPixSz->AddGrowableRow( 2 ); fgPixSz->AddGrowableRow( 3 ); + fgPixSz->SetFlexibleDirection( wxHORIZONTAL ); + fgPixSz->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); + sbPixSize->Add( fgPixSz, 1, wxEXPAND, 5 ); + + //bottom box is 8x5 grid + wxFlexGridSizer* fgPtCen = new wxFlexGridSizer( 5, 8, 0, 0 ); + fgPtCen->AddGrowableCol( 0 ); fgPtCen->AddGrowableCol( 4 ); fgPtCen->AddGrowableCol( 7 ); + fgPtCen->AddGrowableRow( 0 ); fgPtCen->AddGrowableRow( 1 ); fgPtCen->AddGrowableRow( 2 ); fgPtCen->AddGrowableRow( 3 ); fgPtCen->AddGrowableRow( 4 ); + fgPtCen->SetFlexibleDirection( wxHORIZONTAL ); + fgPtCen->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); + sbPatCen->Add( fgPtCen, 1, wxEXPAND, 5 ); + + //build all labels + wxStaticText* staTxtBin = new wxStaticText( sbPixSize->GetStaticBox(), wxID_ANY, wxT("Binning" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtBin ->Wrap(-1); + wxStaticText* staTxtX = new wxStaticText( sbPixSize->GetStaticBox(), wxID_ANY, wxT("x" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtX ->Wrap(-1); + wxStaticText* staTxtUnbW = new wxStaticText( sbPixSize->GetStaticBox(), wxID_ANY, wxT("Unbinned Detector Width"), wxDefaultPosition, wxDefaultSize, 0 ); staTxtUnbW ->Wrap(-1); + wxStaticText* staTxtPix = new wxStaticText( sbPixSize->GetStaticBox(), wxID_ANY, wxT("pix" ), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT ); staTxtPix ->Wrap(-1); + wxStaticText* staTxtPxSz = new wxStaticText( sbPixSize->GetStaticBox(), wxID_ANY, wxT("Binned Pixel Size" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtPxSz ->Wrap(-1); + wxStaticText* staTxtUm = new wxStaticText( sbPixSize->GetStaticBox(), wxID_ANY, wxT("um" ), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT ); staTxtUm ->Wrap(-1); + wxStaticText* staTxtDetW = new wxStaticText( sbPixSize->GetStaticBox(), wxID_ANY, wxT("Detector Width" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtDetW ->Wrap(-1); + wxStaticText* staTxtMm = new wxStaticText( sbPixSize->GetStaticBox(), wxID_ANY, wxT("mm" ), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT ); staTxtMm ->Wrap(-1); + wxStaticText* staTxtEmSft = new wxStaticText( sbPatCen ->GetStaticBox(), wxID_ANY, wxT("EMsoft" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtEmSft->Wrap(-1); + wxStaticText* staTxtPcx = new wxStaticText( sbPatCen ->GetStaticBox(), wxID_ANY, wxT("pcx" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtPcx ->Wrap(-1); + wxStaticText* staTxtPcxU = new wxStaticText( sbPatCen ->GetStaticBox(), wxID_ANY, wxT("pix" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtPcxU ->Wrap(-1); + wxStaticText* staTxtXstar = new wxStaticText( sbPatCen ->GetStaticBox(), wxID_ANY, wxT("x*" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtXstar->Wrap(-1); + wxStaticText* staTxtPcy = new wxStaticText( sbPatCen ->GetStaticBox(), wxID_ANY, wxT("pcy" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtPcy ->Wrap(-1); + wxStaticText* staTxtPcyU = new wxStaticText( sbPatCen ->GetStaticBox(), wxID_ANY, wxT("pix" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtPcyU ->Wrap(-1); + wxStaticText* staTxtYstar = new wxStaticText( sbPatCen ->GetStaticBox(), wxID_ANY, wxT("y*" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtYstar->Wrap(-1); + wxStaticText* staTxtL = new wxStaticText( sbPatCen ->GetStaticBox(), wxID_ANY, wxT("L" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtL ->Wrap(-1); + wxStaticText* staTxtLU = new wxStaticText( sbPatCen ->GetStaticBox(), wxID_ANY, wxT("um" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtLU ->Wrap(-1); + wxStaticText* staTxtZstar = new wxStaticText( sbPatCen ->GetStaticBox(), wxID_ANY, wxT("z*" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtZstar->Wrap(-1); + wxStaticText* staTxtTlt = new wxStaticText( sbPatCen ->GetStaticBox(), wxID_ANY, wxT("Detector Tilt" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtTlt ->Wrap(-1); + wxStaticText* staTxtDeg = new wxStaticText( sbPatCen ->GetStaticBox(), wxID_ANY, wxT("deg" ), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT ); staTxtDeg ->Wrap(-1); + + //set text validator ranges + valBin.SetRange( 1 , 16 ); + valDlt.SetRange( 1 , 1200 );//this is [5,75] um * 16x binning (actualy [1,75] to make text entry easier) + valWdt.SetRange( 1 , 100 );//[1,100] mm + valPc .SetRange(-1024 , 1024 );//this is x/y* from -1,1 for a 1k detector + valL .SetRange( 1 , 50000 );//[1,50] mm (actualy 1um to 50mm to make text entry easier) + xyStar.SetRange(-1 , 1 );//should be ~0.5 and ~0.7 for pcx and pcy respectively + zStar .SetRange( 0.01f, 2 );//minimum is 1 mm for 100 mm detector + valTlt.SetRange(-90 , 90 ); + + //specify validator precisions, max 6 for float + valDlt.SetPrecision(2); + valWdt.SetPrecision(4); + valPc .SetPrecision(3); + valL .SetPrecision(2); + xyStar.SetPrecision(6); + zStar .SetPrecision(6); + valTlt.SetPrecision(1); + + //build elements for pixel size box + wxString m_chcVendorChoices[] = { wxT("Bruker"), wxT("EDAX"), wxT("Oxford") }; + int m_chcVendorNChoices = sizeof( m_chcVendorChoices ) / sizeof( wxString ); + m_txtBin = new wxTextCtrl( sbPixSize->GetStaticBox(), wxID_ANY, wxT("" ), wxDefaultPosition, wxDefaultSize, wxTE_CENTER , valBin ); + m_txtUnDetW = new wxTextCtrl( sbPixSize->GetStaticBox(), wxID_ANY, wxT("" ), wxDefaultPosition, wxDefaultSize, wxTE_CENTER|wxTE_READONLY ); + m_txtPxSz = new wxTextCtrl( sbPixSize->GetStaticBox(), wxID_ANY, wxT("" ), wxDefaultPosition, wxDefaultSize, wxTE_CENTER , valDlt ); + m_txtDetW = new wxTextCtrl( sbPixSize->GetStaticBox(), wxID_ANY, wxT("" ), wxDefaultPosition, wxDefaultSize, wxTE_CENTER , valWdt ); + + //build elements for pattern center box + m_txtPcx = new wxTextCtrl( sbPatCen ->GetStaticBox(), wxID_ANY, wxT("" ), wxDefaultPosition, wxDefaultSize, wxTE_CENTER , valPc ); + m_txtXstar = new wxTextCtrl( sbPatCen ->GetStaticBox(), wxID_ANY, wxT("" ), wxDefaultPosition, wxDefaultSize, wxTE_CENTER , xyStar ); + m_txtPcy = new wxTextCtrl( sbPatCen ->GetStaticBox(), wxID_ANY, wxT("" ), wxDefaultPosition, wxDefaultSize, wxTE_CENTER , valPc ); + m_txtYstar = new wxTextCtrl( sbPatCen ->GetStaticBox(), wxID_ANY, wxT("" ), wxDefaultPosition, wxDefaultSize, wxTE_CENTER , xyStar ); + m_txtL = new wxTextCtrl( sbPatCen ->GetStaticBox(), wxID_ANY, wxT("" ), wxDefaultPosition, wxDefaultSize, wxTE_CENTER , valL ); + m_txtZstar = new wxTextCtrl( sbPatCen ->GetStaticBox(), wxID_ANY, wxT("" ), wxDefaultPosition, wxDefaultSize, wxTE_CENTER , zStar ); + m_txtTlt = new wxTextCtrl( sbPatCen ->GetStaticBox(), wxID_ANY, wxT("" ), wxDefaultPosition, wxDefaultSize, wxTE_CENTER , valTlt ); + m_chcVendor = new wxChoice ( sbPatCen ->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, m_chcVendorNChoices, m_chcVendorChoices, 0 ); + m_btnFit = new wxButton ( sbPatCen ->GetStaticBox(), wxID_ANY, wxT("Fit..."), wxDefaultPosition, wxDefaultSize, 0 ); + m_chcVendor->SetSelection( -1 ); + m_btnFit->Enable(false);//need to write fit routine + + //assemble pixel size grid + fgPixSz->Add( staTxtBin , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgPixSz->Add( m_txtBin , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT , 5 ); + fgPixSz->Add( staTxtX , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT , 5 ); + + fgPixSz->Add( staTxtUnbW , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgPixSz->Add( m_txtUnDetW, 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT , 5 ); + fgPixSz->Add( staTxtPix , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT , 5 ); + + fgPixSz->Add( staTxtPxSz , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgPixSz->Add( m_txtPxSz , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT , 5 ); + fgPixSz->Add( staTxtUm , 0, wxALL|wxALIGN_CENTER_VERTICAL , 5 ); + + fgPixSz->Add( staTxtDetW , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgPixSz->Add( m_txtDetW , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT , 5 ); + fgPixSz->Add( staTxtMm , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT , 5 ); + + //assemble pattern center grid + fgPtCen->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgPtCen->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgPtCen->Add( staTxtEmSft, 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgPtCen->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgPtCen->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgPtCen->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgPtCen->Add( m_chcVendor, 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgPtCen->Add( 0 , 0, 1 , wxEXPAND , 5 ); + + fgPtCen->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgPtCen->Add( staTxtPcx , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgPtCen->Add( m_txtPcx , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT , 5 ); + fgPtCen->Add( staTxtPcxU , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT , 5 ); + fgPtCen->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgPtCen->Add( staTxtXstar, 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgPtCen->Add( m_txtXstar , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT , 5 ); + fgPtCen->Add( 0 , 0, 1 , wxEXPAND , 5 ); + + fgPtCen->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgPtCen->Add( staTxtPcy , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgPtCen->Add( m_txtPcy , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT , 5 ); + fgPtCen->Add( staTxtPcyU , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT , 5 ); + fgPtCen->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgPtCen->Add( staTxtYstar, 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgPtCen->Add( m_txtYstar , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT , 5 ); + fgPtCen->Add( 0 , 0, 1 , wxEXPAND , 5 ); + + fgPtCen->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgPtCen->Add( staTxtL , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgPtCen->Add( m_txtL , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT , 5 ); + fgPtCen->Add( staTxtLU , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT , 5 ); + fgPtCen->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgPtCen->Add( staTxtZstar, 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgPtCen->Add( m_txtZstar , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT , 5 ); + fgPtCen->Add( 0 , 0, 1 , wxEXPAND , 5 ); + + fgPtCen->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgPtCen->Add( staTxtTlt , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgPtCen->Add( m_txtTlt , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT , 5 ); + fgPtCen->Add( staTxtDeg , 0, wxALL|wxALIGN_CENTER_VERTICAL , 5 ); + fgPtCen->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgPtCen->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgPtCen->Add( m_btnFit , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT , 5 ); + fgPtCen->Add( 0 , 0, 1 , wxEXPAND , 5 ); + + this->SetSizer( bPatCent ); + this->Layout(); + EnablePatCen(false); + + // Connect Events + m_txtBin ->Connect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( PatternCenterPanel::BinningChanged ), NULL, this ); + m_txtPxSz ->Connect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( PatternCenterPanel::PixSzChanged ), NULL, this ); + m_txtDetW ->Connect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( PatternCenterPanel::DetWidChanged ), NULL, this ); + m_chcVendor->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( PatternCenterPanel::VendorChanged ), NULL, this ); + m_txtPcx ->Connect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( PatternCenterPanel::PcxChanged ), NULL, this ); + m_txtXstar ->Connect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( PatternCenterPanel::XstarChanged ), NULL, this ); + m_txtPcy ->Connect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( PatternCenterPanel::PcyChanged ), NULL, this ); + m_txtYstar ->Connect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( PatternCenterPanel::YstarChanged ), NULL, this ); + m_txtL ->Connect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( PatternCenterPanel::LChanged ), NULL, this ); + m_txtZstar ->Connect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( PatternCenterPanel::ZstarChanged ), NULL, this ); + m_txtTlt ->Connect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( PatternCenterPanel::TltChanged ), NULL, this ); + m_btnFit ->Connect( wxEVT_COMMAND_BUTTON_CLICKED , wxCommandEventHandler( PatternCenterPanel::DoFit ), NULL, this ); +} + +PatternCenterPanel::~PatternCenterPanel() { + // Disconnect Events + m_txtBin ->Disconnect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( PatternCenterPanel::BinningChanged ), NULL, this ); + m_txtPxSz ->Disconnect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( PatternCenterPanel::PixSzChanged ), NULL, this ); + m_txtDetW ->Disconnect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( PatternCenterPanel::DetWidChanged ), NULL, this ); + m_chcVendor->Disconnect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( PatternCenterPanel::VendorChanged ), NULL, this ); + m_txtPcx ->Disconnect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( PatternCenterPanel::PcxChanged ), NULL, this ); + m_txtXstar ->Disconnect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( PatternCenterPanel::XstarChanged ), NULL, this ); + m_txtPcy ->Disconnect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( PatternCenterPanel::PcyChanged ), NULL, this ); + m_txtYstar ->Disconnect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( PatternCenterPanel::YstarChanged ), NULL, this ); + m_txtL ->Disconnect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( PatternCenterPanel::LChanged ), NULL, this ); + m_txtZstar ->Disconnect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( PatternCenterPanel::ZstarChanged ), NULL, this ); + m_txtTlt ->Disconnect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( PatternCenterPanel::TltChanged ), NULL, this ); + m_btnFit ->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED , wxCommandEventHandler( PatternCenterPanel::DoFit ), NULL, this ); +} + +#endif//_PAT_CEN_PAN_H_ diff --git a/include/wx/PatternLoadPanel.h b/include/wx/PatternLoadPanel.h new file mode 100644 index 0000000..4e59944 --- /dev/null +++ b/include/wx/PatternLoadPanel.h @@ -0,0 +1,527 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _PAT_LOAD_PAN_H_ +#define _PAT_LOAD_PAN_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wx/ValidityPanel.h" + +/////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +/// Class PatternLoadPanelBase +/////////////////////////////////////////////////////////////////////////////// +class PatternLoadPanelBase : public ValidityPanel { + wxFilePickerCtrl* m_filePicker; + wxTextCtrl * m_txtW ; + wxTextCtrl * m_txtH ; + wxTextCtrl * m_txtBit ; + wxTextCtrl * m_txtNum ; + wxTextCtrl * m_txtPrvCnt ; + wxButton * m_btnPrv ; + wxTextCtrl * m_mskRad ; + wxCheckBox * m_chkBckg ; + wxTextCtrl * m_txtNReg ; + + wxIntegerValidator valSize ; + wxIntegerValidator valPrvNum; + wxIntegerValidator valCirc ; + wxIntegerValidator valAhe ; + + protected: + + // Virtual event handlers, overide them in your derived class + virtual void FileChanged( wxFileDirPickerEvent& event ) { testValid(); event.Skip(); } + virtual void WidthChanged( wxCommandEvent& event ) { testValid(); event.Skip(); } + virtual void HeightChanged( wxCommandEvent& event ) { testValid(); event.Skip(); } + virtual void NumPrvChanged( wxCommandEvent& event ) { testValid(); event.Skip(); } + virtual void DoPreview( wxCommandEvent& event ) { event.Skip(); } + virtual void CircChanged( wxCommandEvent& event ) { testValid(); event.Skip(); } + virtual void AheChanged( wxCommandEvent& event ) { testValid(); event.Skip(); } + + + void EnableWH(const bool enable) {m_txtW->SetEditable(enable); m_txtH->SetEditable(enable);} + bool IsEnabledWH() const {return m_txtW->IsEnabled() && m_txtH->IsEnabled();} + void EnablePrv(const bool enable) {m_btnPrv->Enable(enable);} + void ClearInfo(); + + public: + + PatternLoadPanelBase( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 400,400 ), long style = wxTAB_TRAVERSAL, const wxString& name = wxEmptyString ); + ~PatternLoadPanelBase(); + + //@brief: sanity check the current state + //@return: true if the values parsed from the panel are reasonable, false otherwise + //@note : checks for has a file, has detector sizes, and has an AHE value + std::string validMsg() const { + if(!wxFileExists(getFile())) return "pattern file doesn't exist"; + if(!hasW()) return "pattern width empty"; + if(!hasH()) return "pattern height empty"; + if(0 == getNum()) return "0 patterns"; + if(!hasNprv()) return "preview count empty"; + if(!hasCirc()) return "mask radius empty"; + if(!hasNreg()) return "AHE tiles empty"; + return ""; + } + + //@brief: get values from editable fields + wxString getFile() const {return m_filePicker->GetPath();} + long getW () const {long v; m_txtW ->GetLineText(0).ToLong(&v); return v;} + long getH () const {long v; m_txtH ->GetLineText(0).ToLong(&v); return v;} + long getBit () const {long v; m_txtBit ->GetLineText(0).ToLong(&v); return v;} + long getNum () const {long v; m_txtNum ->GetLineText(0).ToLong(&v); return v;} + long getNprv() const {long v; m_txtPrvCnt->GetLineText(0).ToLong(&v); return v;} + long getCirc() const {long v; m_mskRad ->GetLineText(0).ToLong(&v); return v;} + bool getBckg() const {return m_chkBckg->IsChecked();} + long getNreg() const {long v; m_txtNReg ->GetLineText(0).ToLong(&v); return v;} + + //@brief: check if text fields are empty + bool hasW () const {return !m_txtW ->GetLineText(0).IsEmpty();} + bool hasH () const {return !m_txtH ->GetLineText(0).IsEmpty();} + bool hasNprv() const {return !m_txtPrvCnt->GetLineText(0).IsEmpty();} + bool hasCirc() const {return !m_mskRad ->GetLineText(0).IsEmpty();} + bool hasNreg() const {return !m_txtNReg ->GetLineText(0).IsEmpty();} + + //@brief: set values of text fields + void ClearFile() {wxFileName fn; m_filePicker->SetFileName(fn);} + void setW (int w) {m_txtW ->Clear(); m_txtW ->operator<<(w);} + void setH (int h) {m_txtH ->Clear(); m_txtH ->operator<<(h);} + void setBit (int b) {m_txtBit ->Clear(); m_txtBit ->operator<<(b);} + void setNum (int n); + void setNprv(int n) {m_txtPrvCnt->Clear(); m_txtPrvCnt ->operator<<(n);} + void setCirc(int n) {m_mskRad ->Clear(); m_mskRad ->operator<<(n);} + void setBckg(bool b) { m_chkBckg ->SetValue (b);} + void setNreg(int n) {m_txtNReg ->Clear(); m_txtNReg ->operator<<(n);} +}; + +#include +#include "modality/ebsd/nml.hpp" + +class PatternLoadPanel : public PatternLoadPanelBase { + std::string aux;//auxiliary string for pattern files + size_t fileBytes; + + int imW, imH; + std::shared_ptr< std::vector< std::vector > > images;//preview images + + void DimChanged(); + + protected: + + //make sure file is valid when changed + void FileChanged( wxFileDirPickerEvent& event ); + void NumPrvChanged( wxFileDirPickerEvent& event ) {InavlidateImages(); event.Skip();} + + //update pattern number estimate if width/height aren't known from file + void WidthChanged( wxCommandEvent& event ) {DimChanged();} + void HeightChanged( wxCommandEvent& event ) {DimChanged();} + + //launch pattern preview on button click + void DoPreview( wxCommandEvent& event ); + + public: + bool LoadImages(); + void InavlidateImages() {images->clear(), imW = -1, imH = -1;} + bool ImagesValid() const {return !images->empty() && getNprv() == images->size() && getW() == imW && getH() == imH;} + std::shared_ptr< std::vector< std::vector > > GetImages() {return images;} + PatternLoadPanel( wxWindow* parent, wxWindowID id = wxID_ANY) : PatternLoadPanelBase(parent, id) {images = std::make_shared< std::vector< std::vector > >(); InavlidateImages();} + + bool validFile() const {return wxFileExists(getFile());} + std::string getAux() const {return aux;} + +}; + +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version Oct 26 2018) +// http://www.wxformbuilder.org/ +// +// PLEASE DO *NOT* EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// + +void PatternLoadPanelBase::ClearInfo() { + m_txtW->Clear(); + m_txtH->Clear(); + EnableWH(false); + m_txtBit->ChangeValue("Unknown"); + m_txtNum->ChangeValue("Unknown"); + m_txtPrvCnt->ChangeValue("1"); + valPrvNum.SetMax(1); +} + +void PatternLoadPanelBase::setNum (int n) { + m_txtNum ->Clear(); + m_txtNum ->operator<<(n); + valPrvNum.SetMax(n); + m_txtPrvCnt->SetValidator(valPrvNum); + if(getNprv() > n) { + setNprv(std::max(n,1)); + } else { + setNprv(std::min(n,100)); + } +} + +PatternLoadPanelBase::PatternLoadPanelBase( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name ) : ValidityPanel( parent, id, pos, size, style, name ) { + //split panel into 3 vertical boxes + wxBoxSizer * bPatInfo = new wxBoxSizer( wxVERTICAL ); + wxStaticBoxSizer* sbPatFile = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, wxT("Pattern File" ) ), wxVERTICAL ); + wxStaticBoxSizer* sbPatInfo = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, wxT("Pattern Info" ) ), wxVERTICAL ); + wxStaticBoxSizer* sbImPrc = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, wxT("Image Processing") ), wxVERTICAL ); + bPatInfo->Add( sbPatFile, 0, wxEXPAND, 5 ); + bPatInfo->Add( sbPatInfo, 1, wxEXPAND, 5 ); + bPatInfo->Add( sbImPrc , 1, wxEXPAND, 5 ); + + //middle box is 6x4 grid + wxFlexGridSizer* fgPatInfo = new wxFlexGridSizer( 4, 6, 0, 0 ); + fgPatInfo->AddGrowableCol( 0 ); fgPatInfo->AddGrowableCol( 2 ); fgPatInfo->AddGrowableCol( 5 ); + fgPatInfo->AddGrowableRow( 0 ); fgPatInfo->AddGrowableRow( 1 ); fgPatInfo->AddGrowableRow( 2 ); fgPatInfo->AddGrowableRow( 3 ); + fgPatInfo->SetFlexibleDirection( wxHORIZONTAL ); + fgPatInfo->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); + sbPatInfo->Add( fgPatInfo, 1, wxEXPAND, 5 ); + + //lower box is 6x2 grid + wxFlexGridSizer* fgImPrc = new wxFlexGridSizer( 4, 6, 0, 0 ); + fgImPrc->AddGrowableCol( 0 ); fgImPrc->AddGrowableCol( 2 ); fgImPrc->AddGrowableCol( 5 ); + fgImPrc->AddGrowableRow( 0 ); fgImPrc->AddGrowableRow( 1 ); fgImPrc->AddGrowableRow( 2 ); fgImPrc->AddGrowableRow( 3 ); + fgImPrc->SetFlexibleDirection( wxHORIZONTAL ); + fgImPrc->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); + sbImPrc->Add( fgImPrc, 1, wxEXPAND, 5 ); + + //build all labels + wxStaticText* staTxtBinDetW = new wxStaticText( sbPatInfo->GetStaticBox(), wxID_ANY, wxT("Binned Detector Width" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtBinDetW->Wrap(-1); + wxStaticText* staTxtPixW = new wxStaticText( sbPatInfo->GetStaticBox(), wxID_ANY, wxT("pix" ), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT ); staTxtPixW ->Wrap(-1); + wxStaticText* staTxtH = new wxStaticText( sbPatInfo->GetStaticBox(), wxID_ANY, wxT("Binned Detector Height"), wxDefaultPosition, wxDefaultSize, 0 ); staTxtH ->Wrap(-1); + wxStaticText* staTxtBitH = new wxStaticText( sbPatInfo->GetStaticBox(), wxID_ANY, wxT("pix" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtBitH ->Wrap(-1); + wxStaticText* staTxtBitDpt = new wxStaticText( sbPatInfo->GetStaticBox(), wxID_ANY, wxT("Bitdepth" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtBitDpt ->Wrap(-1); + wxStaticText* staTxtBits = new wxStaticText( sbPatInfo->GetStaticBox(), wxID_ANY, wxT("bits" ), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT ); staTxtBits ->Wrap(-1); + wxStaticText* staTxtNum = new wxStaticText( sbPatInfo->GetStaticBox(), wxID_ANY, wxT("Number" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtNum ->Wrap(-1); + wxStaticText* staTxtPats = new wxStaticText( sbPatInfo->GetStaticBox(), wxID_ANY, wxT("pats" ), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT ); staTxtPats ->Wrap(-1); + wxStaticText* staTxtPrvCnt = new wxStaticText( sbImPrc ->GetStaticBox(), wxID_ANY, wxT("Preview Count" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtPrvCnt ->Wrap(-1); + + wxStaticText* staTxtCirc = new wxStaticText( sbImPrc ->GetStaticBox(), wxID_ANY, wxT("Circular Mask Radius" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtCirc ->Wrap(-1); + wxStaticText* staTxtCircU = new wxStaticText( sbImPrc ->GetStaticBox(), wxID_ANY, wxT("pix" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtCircU ->Wrap(-1); + wxStaticText* staTxtNreg = new wxStaticText( sbImPrc ->GetStaticBox(), wxID_ANY, wxT("Adaptive Histogram Eq."), wxDefaultPosition, wxDefaultSize, 0 ); staTxtNreg ->Wrap(-1); + wxStaticText* staTxtNregU = new wxStaticText( sbImPrc ->GetStaticBox(), wxID_ANY, wxT("tiles" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtNregU ->Wrap(-1); + + //set validator ranges + valSize .SetRange( 1, 8192); + valPrvNum.SetRange( 1, 1 ); + valCirc .SetRange(-1, 4096); + valAhe .SetRange( 0, 32 ); + + //build elements for pattern file box and assemble + wxString wildCards = wxT("EBSD Pattern Files (*.h5, *.upx, *.ebsp, or *.data)|*.h5;*.hdf;*.hdf5;*.up1;*.up2;*.ebsp;*.data"); + m_filePicker = new wxFilePickerCtrl( sbPatFile->GetStaticBox(), wxID_ANY, wxEmptyString, wxT("Select a file"), wildCards, wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE|wxFLP_SMALL ); + sbPatFile->Add( m_filePicker, 1, wxALL|wxEXPAND, 5 ); + + //build elements for pattern info box + m_txtW = new wxTextCtrl( sbPatInfo->GetStaticBox(), wxID_ANY, wxT("" ), wxDefaultPosition, wxDefaultSize, wxTE_CENTER , valSize ); + m_txtH = new wxTextCtrl( sbPatInfo->GetStaticBox(), wxID_ANY, wxT("" ), wxDefaultPosition, wxDefaultSize, wxTE_CENTER , valSize ); + m_txtBit = new wxTextCtrl( sbPatInfo->GetStaticBox(), wxID_ANY, wxT("Unknown"), wxDefaultPosition, wxDefaultSize, wxTE_CENTER|wxTE_READONLY ); + m_txtNum = new wxTextCtrl( sbPatInfo->GetStaticBox(), wxID_ANY, wxT("Unknown"), wxDefaultPosition, wxDefaultSize, wxTE_CENTER|wxTE_READONLY ); + + //build elements for image processing box + m_txtPrvCnt = new wxTextCtrl( sbImPrc->GetStaticBox(), wxID_ANY, wxT("1" ), wxDefaultPosition, wxDefaultSize, wxTE_CENTER|wxTE_PROCESS_ENTER, valPrvNum ); + m_btnPrv = new wxButton ( sbImPrc->GetStaticBox(), wxID_ANY, wxT("Preview..." ), wxDefaultPosition, wxDefaultSize, 0 ); + m_mskRad = new wxTextCtrl( sbImPrc->GetStaticBox(), wxID_ANY, wxT("-1" ), wxDefaultPosition, wxDefaultSize, wxTE_CENTER , valCirc ); + m_chkBckg = new wxCheckBox( sbImPrc->GetStaticBox(), wxID_ANY, wxT("Gaussian Background"), wxDefaultPosition, wxDefaultSize, 0 ); + m_txtNReg = new wxTextCtrl( sbImPrc->GetStaticBox(), wxID_ANY, wxT("0" ), wxDefaultPosition, wxDefaultSize, wxTE_CENTER , valAhe ); + m_btnPrv->Enable(false); + + //assemble pattern info grid + fgPatInfo->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgPatInfo->Add( staTxtBinDetW, 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgPatInfo->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgPatInfo->Add( m_txtW , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT , 5 ); + fgPatInfo->Add( staTxtPixW , 0, wxALL|wxALIGN_CENTER_VERTICAL , 5 ); + fgPatInfo->Add( 0 , 0, 1 , wxEXPAND , 5 ); + + fgPatInfo->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgPatInfo->Add( staTxtH , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgPatInfo->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgPatInfo->Add( m_txtH , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT , 5 ); + fgPatInfo->Add( staTxtBitH , 0, wxALL|wxALIGN_CENTER_VERTICAL , 5 ); + fgPatInfo->Add( 0 , 0, 1 , wxEXPAND , 5 ); + + fgPatInfo->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgPatInfo->Add( staTxtBitDpt , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgPatInfo->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgPatInfo->Add( m_txtBit , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT , 5 ); + fgPatInfo->Add( staTxtBits , 0, wxALL|wxALIGN_CENTER_VERTICAL , 5 ); + fgPatInfo->Add( 0 , 0, 1 , wxEXPAND , 5 ); + + fgPatInfo->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgPatInfo->Add( staTxtNum , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgPatInfo->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgPatInfo->Add( m_txtNum , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT , 5 ); + fgPatInfo->Add( staTxtPats , 0, wxALL|wxALIGN_CENTER_VERTICAL , 5 ); + fgPatInfo->Add( 0 , 0, 1 , wxEXPAND , 5 ); + + //assemble image processing grid + fgImPrc ->Add( 0 , 0 , 1, wxEXPAND , 5 ); + fgImPrc ->Add( staTxtPrvCnt , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgImPrc ->Add( 0 , 0 , 1, wxEXPAND , 5 ); + fgImPrc ->Add( m_txtPrvCnt , 0, wxALL|wxALIGN_CENTER_VERTICAL , 5 ); + fgImPrc ->Add( m_btnPrv , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxEXPAND , 5 ); + fgImPrc ->Add( 0 , 0 , 1, wxEXPAND , 5 ); + + fgImPrc ->Add( 0 , 0 , 1, wxEXPAND , 5 ); + fgImPrc ->Add( staTxtCirc , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgImPrc ->Add( 0 , 0 , 1, wxEXPAND , 5 ); + fgImPrc ->Add( m_mskRad , 0, wxALL|wxALIGN_CENTER_VERTICAL , 5 ); + fgImPrc ->Add( staTxtCircU , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxEXPAND , 5 ); + fgImPrc ->Add( 0 , 0 , 1, wxEXPAND , 5 ); + + fgImPrc ->Add( 0 , 0 , 1, wxEXPAND , 5 ); + fgImPrc ->Add( 0 , 0 , 1, wxEXPAND , 5 ); + fgImPrc ->Add( 0 , 0 , 1, wxEXPAND , 5 ); + fgImPrc ->Add( m_chkBckg , 0, wxALL|wxALIGN_CENTER_VERTICAL , 5 ); + fgImPrc ->Add( 0 , 0 , 1, wxEXPAND , 5 ); + fgImPrc ->Add( 0 , 0 , 1, wxEXPAND , 5 ); + + fgImPrc ->Add( 0 , 0 , 1, wxEXPAND , 5 ); + fgImPrc ->Add( staTxtNreg , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgImPrc ->Add( 0 , 0 , 1, wxEXPAND , 5 ); + fgImPrc ->Add( m_txtNReg , 0, wxALL|wxALIGN_CENTER_VERTICAL , 5 ); + fgImPrc ->Add( staTxtNregU , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxEXPAND , 5 ); + fgImPrc ->Add( 0 , 0 , 1, wxEXPAND , 5 ); + + this->SetSizer( bPatInfo ); + this->Layout(); + + // Connect Events + m_filePicker->Connect( wxEVT_COMMAND_FILEPICKER_CHANGED, wxFileDirPickerEventHandler( PatternLoadPanelBase::FileChanged ), NULL, this ); + m_txtW ->Connect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler ( PatternLoadPanelBase::WidthChanged ), NULL, this ); + m_txtH ->Connect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler ( PatternLoadPanelBase::HeightChanged ), NULL, this ); + m_txtPrvCnt ->Connect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler ( PatternLoadPanelBase::NumPrvChanged ), NULL, this ); + m_txtPrvCnt ->Connect( wxEVT_COMMAND_TEXT_ENTER , wxCommandEventHandler ( PatternLoadPanelBase::DoPreview ), NULL, this ); + m_btnPrv ->Connect( wxEVT_COMMAND_BUTTON_CLICKED , wxCommandEventHandler ( PatternLoadPanelBase::DoPreview ), NULL, this ); + m_mskRad ->Connect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler ( PatternLoadPanelBase::CircChanged ), NULL, this ); + m_txtNReg ->Connect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler ( PatternLoadPanelBase::AheChanged ), NULL, this ); +} + +PatternLoadPanelBase::~PatternLoadPanelBase() { + // Disconnect Events + m_filePicker->Disconnect( wxEVT_COMMAND_FILEPICKER_CHANGED, wxFileDirPickerEventHandler( PatternLoadPanelBase::FileChanged ), NULL, this ); + m_txtW ->Disconnect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler ( PatternLoadPanelBase::WidthChanged ), NULL, this ); + m_txtH ->Disconnect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler ( PatternLoadPanelBase::HeightChanged ), NULL, this ); + m_txtPrvCnt ->Disconnect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler ( PatternLoadPanelBase::NumPrvChanged ), NULL, this ); + m_txtPrvCnt ->Disconnect( wxEVT_COMMAND_TEXT_ENTER , wxCommandEventHandler ( PatternLoadPanelBase::DoPreview ), NULL, this ); + m_btnPrv ->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED , wxCommandEventHandler ( PatternLoadPanelBase::DoPreview ), NULL, this ); + m_mskRad ->Disconnect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler ( PatternLoadPanelBase::CircChanged ), NULL, this ); + m_txtNReg ->Disconnect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler ( PatternLoadPanelBase::AheChanged ), NULL, this ); + +} + +//////////////////////////////////////////////////////////////////// + +#include +#include +#include "PatternPreviewPanel.h" +#include "SinglePanelDialog.h" + +void PatternLoadPanel::DimChanged() { + if(32 == getBit() && IsEnabledWH()) {//a *.data file is currently loaded + size_t bytes = 4 * getW() * getH();//patterns per byte + if(!(hasW() && hasH())) bytes = 0; + size_t numPat = 0; + if(bytes > 0) { + numPat = fileBytes / bytes; + } + setNum(numPat); + InavlidateImages(); + testValid(); + } +} + +#include "modality/ebsd/pattern.hpp" + +void PatternLoadPanel::FileChanged( wxFileDirPickerEvent& event ) { + EnablePrv(false); + InavlidateImages(); + ClearInfo(); + if(wxFileExists(getFile())) { + //get aux path for hdf files + if(H5::H5File::isHdf5(getFile().ToStdString().c_str())) { + std::vector auxPaths = emsphinx::ebsd::PatternFile::SearchH5(getFile().ToStdString()); + std::sort(auxPaths.begin(), auxPaths.end()); + + //now we have all viable datasets, make sure we have at least 1 choice + if(auxPaths.empty()) { + ClearFile(); + wxMessageDialog msgDlg(this, "No suitable datasets found", "Error"); + msgDlg.ShowModal(); + return; + } + + //select dataset if multiple are found + int idx = 0;//first choice + if(auxPaths.size() > 1) {//there are multiple choices + wxArrayString choices; + for(const std::string& str : auxPaths) choices.Add(str); + wxSingleChoiceDialog dSetSelectDlg(this, wxT("Select Pattern Dataset"), "caption", choices);//2 empty strings are defaultDir and defaultFile + if(dSetSelectDlg.ShowModal() != wxID_OK) return;//user didn't pick a dataset + idx = dSetSelectDlg.GetSelection();//get the selected number + } + aux = auxPaths[idx]; + } else { + aux = ""; + } + + //get dimensions + int w, h; + uint64_t num; + emsphinx::ImageSource::Bits bits; + emsphinx::ebsd::PatternFile::GetFileDims(getFile().ToStdString(), w, h, bits, num, aux); + + //most pattern files have everything we need + if(w > 0 && h > 0 && num > 0) { + switch(bits) { + case emsphinx::ImageSource::Bits::U8 : setBit( 8); break; + case emsphinx::ImageSource::Bits::U16: setBit(16); break; + case emsphinx::ImageSource::Bits::F32: setBit(32); break; + case emsphinx::ImageSource::Bits::UNK: + ClearFile(); + wxMessageDialog msgDlg(this, "Unknown Pattern Bitdepth", "Error"); + msgDlg.ShowModal(); + return; + } + setW(w); setH(h); setNum(num); + EnableWH(false); EnablePrv(true); + } else if(-1 == w && -1 == h && emsphinx::ImageSource::Bits::F32 == bits) {//*.data files have unknown size/count + fileBytes = num; + EnableWH(true); setBit(32); setNum(0); + } else { + ClearFile(); + wxMessageDialog msgDlg(this, "Unknown Pattern Bitdepth", "Error"); + msgDlg.ShowModal(); + return; + } + event.Skip();//keep passing + } + testValid(); +} + +//launch pattern preview on button click +bool PatternLoadPanel::LoadImages() { + if(ImagesValid()) return true;//already up to date + + //TODO: put pattern loading into thread with wait indicator + + //build the pattern file + std::shared_ptr pat; + try { + pat = emsphinx::ebsd::PatternFile::Read(getFile().ToStdString(), aux, getW(), getH()); + if(NULL == pat.get()) throw std::runtime_error("failed to read patterns (nullptr)\n"); + } catch (std::exception& e) { + wxMessageDialog msgDlg(this, e.what(), "Error Reading Patterns"); + msgDlg.ShowModal(); + ClearInfo(); + ClearFile(); + return false; + } + + //actually load the patterns + size_t numLoad = getNprv(); + images->clear(); + std::vector buff(pat->imBytes()); + std::vector buff8(pat->imBytes(), 0);//fill with 0 for emsphinx::ImageSource::Bits::UNK + const double spacing = double(pat->numPat()) / numLoad; + size_t idxNext = 0; + for(size_t i = 0; i < pat->numPat(); i++) { + pat->extract(buff.data(), 1);//read a pattern + if(i == idxNext) { + switch(pat->pixelType()) { + case emsphinx::ImageSource::Bits::UNK: break; + case emsphinx::ImageSource::Bits::U8 : buff8.swap(buff); break; + case emsphinx::ImageSource::Bits::U16: { + uint16_t* ptr = (uint16_t*)buff.data(); + std::pair minMax = std::minmax_element(ptr, ptr + pat->numPix());//compute minimum and maximum quality + const double scale = double(255) / (*minMax.second - *minMax.first); + std::transform(ptr, ptr + pat->numPix(), (uint8_t*)buff8.data(), [&](const uint16_t& v){return (uint8_t)std::round(scale * (v - (*minMax.first)));}); + } break; + case emsphinx::ImageSource::Bits::F32: { + float* ptr = (float*)buff.data(); + std::pair minMax = std::minmax_element(ptr, ptr + pat->numPix());//compute minimum and maximum quality + const float scale = 255.0f / (*minMax.second - *minMax.first); + std::transform(ptr, ptr + pat->numPix(), (uint8_t*)buff8.data(), [&](const float& v){return (uint8_t)std::round(scale * (v - (*minMax.first)));}); + } break; + } + images->push_back(buff8); + idxNext = (size_t) std::round(spacing * images->size()); + } + } + imW = pat->width (); + imH = pat->height(); + return true; +} + +void PatternLoadPanel::DoPreview( wxCommandEvent& event ) { + if(!LoadImages()) return; + SinglePanelDialog dlg(this); + dlg.getPanel()->SetImages(images, imW, imH); + dlg.getPanel()->SetCirc(getCirc()); + dlg.getPanel()->SetBckg(getBckg()); + dlg.getPanel()->SetNreg(getNreg()); + dlg.getPanel()->ForceRedraw(); + if( wxID_OK == dlg.ShowModal() ) { + setCirc(dlg.getPanel()->GetCirc()); + setBckg(dlg.getPanel()->GetBckg()); + setNreg(dlg.getPanel()->GetNreg()); + } +} + +#endif//_PAT_LOAD_PAN_H_ diff --git a/include/wx/PatternPreviewPanel.h b/include/wx/PatternPreviewPanel.h new file mode 100644 index 0000000..a0c1690 --- /dev/null +++ b/include/wx/PatternPreviewPanel.h @@ -0,0 +1,350 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _PAT_PREVIEW_H_ +#define _PAT_PREVIEW_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "ImagePanel.h" + +#include "modality/ebsd/imprc.hpp" + +//@brief: frame for pattern preview +class PatternPreviewPanel : public wxPanel { + std::shared_ptr< std::vector< std::vector > > m_images ; + emsphinx::ebsd::PatternProcessor m_proc ;//image processor + size_t m_idxCur ;//current image index + int m_circCur;//current circular radius + bool m_bckgCur;//current background flag + size_t m_nRegCur;//current nregions + + //manually catch arrow keys (they don't seem to be captured on osx) + void keyLeft (); + void keyRight(); + void keyUp (); + void keyDown (); + void keySpace(); + + //@brief : update the the displayed images + //@param idx : new index to display (must be < m_images->size()) + //@param circ: circular mask radius + //@param bckg: new background subtraction flag + //@param nReg: new nRegions to display + void updateImages(size_t idx, int circ, bool bckg, size_t nReg); + + protected: + wxImagePanel * m_panelRaw ; + wxImagePanel * m_panelPrc ; + wxScrollBar * m_scrollBar; + wxSpinCtrl * m_spinCtlR ; + wxCheckBox * m_bckgChk ; + wxSpinCtrl * m_spinCtlNr; + + // Virtual event handlers, overide them in your derived class + virtual void scrlPat ( wxScrollEvent & event) { updateImages(GetIdx(), m_circCur, m_bckgCur, m_nRegCur); }//@brief: triggered by scroll bar changing [change displayed pattern] + virtual void circPat ( wxSpinEvent & event) { updateImages(m_idxCur, GetCirc(), m_bckgCur, m_nRegCur); }//@brief: triggered by spinner changing [change mask radius] + virtual void bckgPat ( wxCommandEvent& event) { updateImages(m_idxCur, m_circCur, GetBckg(), m_nRegCur); }//@brief: triggered by check box changing [change background subtraction] + virtual void procPat ( wxSpinEvent & event) { updateImages(m_idxCur, m_circCur, m_bckgCur, GetNreg()); }//@brief: triggered by spinner changing [change nregions] + // virtual void procChk ( wxCommandEvent& event);//triggered by check box changing [changing circ mask] + void keyPress( wxKeyEvent & event); + + DECLARE_EVENT_TABLE() + + public: + + PatternPreviewPanel( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 600,400 ), long style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL ); + ~PatternPreviewPanel(); + + long GetIdx () const {return m_scrollBar->GetThumbPosition();} + long GetCirc() const {return m_spinCtlR ->GetValue ();} + bool GetBckg() const {return m_bckgChk ->GetValue ();} + long GetNreg() const {return m_spinCtlNr->GetValue ();} + void SetIdx (const long v) {m_scrollBar->SetThumbPosition(v); updateImages(v , m_circCur, m_bckgCur, m_nRegCur);} + void SetCirc(const long v) {m_spinCtlR ->SetValue (v); updateImages(m_idxCur, v , m_bckgCur, m_nRegCur);} + void SetBckg(const bool v) {m_bckgChk ->SetValue (v); updateImages(m_idxCur, m_circCur, v , m_nRegCur);} + void SetNreg(const long v) {m_spinCtlNr->SetValue (v); updateImages(m_idxCur, m_circCur, m_bckgCur, v );} + + void ForceRedraw() {m_idxCur = -1; updateImages(GetIdx(), m_circCur, m_bckgCur, m_nRegCur);} + + void SetImages(std::shared_ptr< std::vector< std::vector > > im, const size_t w, const size_t h); + +}; + +//there are a bunch of scroll events, so this is easier +BEGIN_EVENT_TABLE(PatternPreviewPanel, wxPanel) + EVT_SCROLL (PatternPreviewPanel::scrlPat ) +END_EVENT_TABLE() + +/////////////////////////////////////////////////////////////////////////// +// PatternPreviewPanel logic Implementation // +/////////////////////////////////////////////////////////////////////////// + +//@brief : update the the displayed images +//@param idx : new index to display (must be < m_images->size()) +//@param circ: circular mask radius +//@param bckg: new background subtraction flag +//@param nReg: new nRegions to display +void PatternPreviewPanel::updateImages(size_t idx, int circ, bool bckg, size_t nReg) { + //handle insufficent images + if(NULL == m_images.get() ? true : idx >= m_images->size()) { + wxImage empty; + m_panelRaw->setImage(empty); + m_panelPrc->setImage(empty); + return; + } + wxSize sz = m_panelRaw->GetSize(); + + //start by updating the image processing paramters (builds a new mask) + const bool imProcChanged = m_circCur != circ || m_bckgCur != bckg|| m_nRegCur != nReg; + if(imProcChanged) { + m_proc.setSize(sz.GetWidth(), sz.GetHeight(), circ, bckg, nReg);//update image processing + mask + if(m_circCur != circ) {//the mask radius changed, update alpha channels + char const * ptr = m_proc.getMask(); + const size_t nPix = sz.GetWidth() * sz.GetHeight(); + std::transform(ptr, ptr + nPix, m_panelRaw->GetAlpha(), [](const char& c){return 0 == c ? 0x00 : 0xFF;}); + std::transform(ptr, ptr + nPix, m_panelPrc->GetAlpha(), [](const char& c){return 0 == c ? 0x00 : 0xFF;}); + m_panelRaw->markStale(); + m_panelPrc->markStale(); + m_panelRaw->Refresh(); + } + m_bckgCur = bckg; + m_nRegCur = nReg; + m_circCur = circ; + } + + //first update the raw image if needed + static bool first = true; + if(m_idxCur != idx || first) {//the input image changed + //loop over image updating + char const * pBuff = m_images->operator[](idx).data(); + for(size_t j = 0; j < sz.GetHeight(); j++) { + for(size_t i = 0; i < sz.GetWidth(); i++) { + m_panelRaw->SetGray(i, j, *pBuff); + ++pBuff; + } + } + m_panelRaw->Refresh(); + m_idxCur = idx; + first = true;//force update on processed image even if nReg is the same + } + + //next update the processed image if needed + if(imProcChanged || first) {//either the input image changed, this is the first pass, or the image processing changed + std::vector ahe = m_images->operator[](idx);//copy input image + m_proc.process((uint8_t*)ahe.data());//do AHE + + //update processed image + char const * pBuff = ahe.data(); + for(size_t j = 0; j < sz.GetHeight(); j++) { + for(size_t i = 0; i < sz.GetWidth(); i++) { + m_panelPrc->SetGray(i, j, *pBuff); + ++pBuff; + } + } + m_panelPrc->Refresh(); + } + first = false; +} + +void PatternPreviewPanel::SetImages(std::shared_ptr< std::vector< std::vector > > img, const size_t w, const size_t h) { + //update image sizes + wxImage im(w, h); + m_panelRaw->setImage(im); + m_panelPrc->setImage(im); + + //update the spin control if needed + const size_t minDim = std::min(w, h); + const double diag = std::hypot(double(w) / 2, double(h) / 2); + m_spinCtlR ->SetRange(-1, (int)std::ceil( diag ) );//don't allow too many regions (at least 3x3 window for each histogram) + m_spinCtlNr->SetRange( 0, std::min(32, minDim / 3));//don't allow too many regions (at least 3x3 window for each histogram) + + //update the scroll bar + int idx = 0; + m_images = img; + + int pos = idx; + int thmSz = m_images->size() > 100 ? m_images->size() / 100 : 1; + int rng = m_images->size(); + int pgSz = std::min(thmSz * 10, rng); + m_scrollBar->SetScrollbar(idx, thmSz, rng, pgSz); + m_scrollBar->Enable(rng > 1); + + //get the minimum size of the panel (from contrl bar) and update minimum height to reflect images + const wxSize& sz = this->GetSizer()->GetMinSize(); + double numIm = double(sz.GetWidth()) / (2 * w);//this is how many times we could fit 2 full sized images size by size + int imH = std::round(numIm * h) + 10;//this is how tall the images should be for the widhts to nicely fill the frame + this->GetSizer()->SetMinSize(wxSize(sz.GetWidth(), sz.GetHeight() + imH));//make images fill the frame + this->SetMinSize(wxSize(sz.GetWidth(), sz.GetHeight() + imH));//make images fill the frame + this->GetSizer()->Fit(GetParent());//force the parent window to grow to accomodate our new size + + //update displayed image + m_circCur = -2;//force mask recalculation + updateImages(idx, GetCirc(), GetBckg(), GetNreg()); +} + +/////////////////////////////////////////////////////////////////////////// +// PatternPreviewPanel wxImplementation // +/////////////////////////////////////////////////////////////////////////// + +//@brief: decrement the scroll bar and update images +void PatternPreviewPanel::keyLeft () { + if( !m_scrollBar->IsEnabled()) return; + if( !m_scrollBar ->HasFocus() ) {m_panelRaw->SetFocus(); m_scrollBar ->SetFocus();}//scorr bar doesn't take focus from spin ctrl on osx + m_scrollBar ->SetThumbPosition(m_scrollBar->GetThumbPosition() - 1); + wxScrollEvent evt; + scrlPat(evt); +} + +//@brief: increment the scroll bar and update images +void PatternPreviewPanel::keyRight() { + if( !m_scrollBar->IsEnabled()) return; + if( !m_scrollBar ->HasFocus() ) {m_panelRaw->SetFocus(); m_scrollBar ->SetFocus();}//scorr bar doesn't take focus from spin ctrl on osx + m_scrollBar ->SetThumbPosition(m_scrollBar->GetThumbPosition() + 1); + wxScrollEvent evt; + scrlPat(evt); +} + +//@brief: increment the spinner and update images +void PatternPreviewPanel::keyUp () { + wxSpinEvent evt; + if( !m_spinCtlNr->HasFocus() ) { + if(m_spinCtlR->HasFocus()) {//dont steal focus from radius spin control + m_spinCtlR->SetValue(m_spinCtlR->GetValue() + 1); + circPat(evt); + return; + } + m_spinCtlNr->SetFocus(); + } + m_spinCtlNr->SetValue(m_spinCtlNr->GetValue() + 1); + procPat(evt); +} + +//@brief: decrement the spinner and update images +void PatternPreviewPanel::keyDown () { + wxSpinEvent evt; + if( !m_spinCtlNr->HasFocus() ) { + if(m_spinCtlR->HasFocus()) {//dont steal focus from radius spin control + m_spinCtlR->SetValue(m_spinCtlR->GetValue() - 1); + circPat(evt); + return; + } + m_spinCtlNr->SetFocus(); + } + m_spinCtlNr->SetValue(m_spinCtlNr->GetValue() - 1); + procPat(evt); +} + +//@brief: toggle check box on space bar +void PatternPreviewPanel::keySpace () { + m_bckgChk->SetValue(!m_bckgChk->GetValue()); + wxCommandEvent evt; + bckgPat(evt); +} + +//@brief: handle arrows globally + auto switch focus +void PatternPreviewPanel::keyPress( wxKeyEvent & event) { + switch(event.GetKeyCode()) { + case WXK_LEFT : keyLeft (); break; + case WXK_RIGHT: keyRight(); break; + case WXK_UP : keyUp (); break; + case WXK_DOWN : keyDown (); break; + case WXK_SPACE: keySpace(); break; + default: event.Skip(); + } +} + +PatternPreviewPanel::PatternPreviewPanel( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style ) : wxPanel( parent, id, pos, size, style ) { + this->SetSizeHints( wxDefaultSize, wxDefaultSize ); + + //build sizers + wxBoxSizer* vSizer = new wxBoxSizer( wxVERTICAL );//main sizer is vertical boxes + wxBoxSizer* hSizerIm = new wxBoxSizer( wxHORIZONTAL );//horizontal box for 2 images + wxBoxSizer* hSizerCtrl = new wxBoxSizer( wxHORIZONTAL );//horizontal box for spin control + + //build elements + m_panelRaw = new wxImagePanel ( this, wxID_ANY , wxDefaultPosition , wxDefaultSize );//raw image + m_panelPrc = new wxImagePanel ( this, wxID_ANY , wxDefaultPosition , wxDefaultSize );//raw image + m_scrollBar = new wxScrollBar ( this, wxID_ANY , wxDefaultPosition , wxDefaultSize , wxSB_HORIZONTAL );//horizontal scroll bar + wxStaticText* txtR = new wxStaticText ( this, wxID_ANY , wxT("Mask Radius") , wxDefaultPosition, wxDefaultSize , 0 );//spinner label + m_spinCtlR = new wxSpinCtrl ( this, wxID_ANY , wxEmptyString , wxDefaultPosition, wxDefaultSize , wxSP_ARROW_KEYS, -1, 0 , -1); + m_bckgChk = new wxCheckBox ( this, wxID_ANY , wxT("Gaussian Background") , wxDefaultPosition, wxDefaultSize );//check box + wxStaticText* txtNReg = new wxStaticText ( this, wxID_ANY , wxT("Histogram Equalization"), wxDefaultPosition, wxDefaultSize , 0 );//spinner label + m_spinCtlNr = new wxSpinCtrl ( this, wxID_ANY , wxEmptyString , wxDefaultPosition, wxDefaultSize , wxSP_ARROW_KEYS, 0, 32, 4);//spinner (initial value 4) + + //build image box + hSizerIm->Add( m_panelRaw, 1, wxALL|wxEXPAND, 5 ); + hSizerIm->Add( m_panelPrc, 1, wxALL|wxEXPAND, 5 ); + + //build controls box + hSizerCtrl->AddStretchSpacer(); + hSizerCtrl->Add( txtR , 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + hSizerCtrl->Add( m_spinCtlR , 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + hSizerCtrl->AddStretchSpacer(); + hSizerCtrl->Add( m_bckgChk , 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + hSizerCtrl->AddStretchSpacer(); + hSizerCtrl->Add( txtNReg , 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + hSizerCtrl->Add( m_spinCtlNr, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + hSizerCtrl->AddStretchSpacer(); + + //assemble into overall frame + vSizer->Add( hSizerIm , 1, wxEXPAND , 5 ); + vSizer->Add( m_scrollBar, 0, wxALL|wxEXPAND, 5 ); + vSizer->Add( hSizerCtrl , 0, wxEXPAND , 5 ); + + //assign sizer to frame + this->SetSizer( vSizer ); + this->Layout(); + this->Centre( wxBOTH ); + + m_spinCtlR ->Connect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler ( PatternPreviewPanel::circPat ), NULL, this ); + m_bckgChk ->Connect( wxEVT_CHECKBOX , wxCommandEventHandler( PatternPreviewPanel::bckgPat ), NULL, this ); + m_spinCtlNr->Connect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler ( PatternPreviewPanel::procPat ), NULL, this ); + this ->Connect( wxEVT_CHAR_HOOK , wxKeyEventHandler ( PatternPreviewPanel::keyPress), NULL, this ); +} + +PatternPreviewPanel::~PatternPreviewPanel() { + m_spinCtlR ->Disconnect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler ( PatternPreviewPanel::circPat ), NULL, this ); + m_bckgChk ->Disconnect( wxEVT_CHECKBOX , wxCommandEventHandler( PatternPreviewPanel::bckgPat ), NULL, this ); + m_spinCtlNr->Disconnect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler ( PatternPreviewPanel::procPat ), NULL, this ); + this ->Disconnect( wxEVT_CHAR_HOOK , wxKeyEventHandler ( PatternPreviewPanel::keyPress), NULL, this ); +} + +#endif//_PAT_PREVIEW_H_ \ No newline at end of file diff --git a/include/wx/PeriodicTablePanel.h b/include/wx/PeriodicTablePanel.h new file mode 100644 index 0000000..3a01614 --- /dev/null +++ b/include/wx/PeriodicTablePanel.h @@ -0,0 +1,206 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _PER_TAB_PAN_H_ +#define _PER_TAB_PAN_H_ + +#include +#include + +//@brief: helper class to store a bitmask of elements +//@note : bits are 1 indexed to e.g. He is at .test(2) not .test(1) +struct ElementMask : public std::bitset<128> {//we only need [1,118] + //@brief : convert to string list of elements + //@return: string representation, e.g. "H, He, Li" + std::string str() const; + + //@brief : get the symbol for an element + //@param z: atomic number + //@return : e.g. "Li" for 3 + static std::string Symbol(const size_t z); +}; + +#include +#include +#include +#include +#include + +//@brief: panel to display a periodic table of toggle buttons +class PeriodicTablePanel : public wxPanel { + std::vector m_btns;//a button for each element in order (0 indexed) + + public: + //@brief : get a mask of which elements are toggled + //@return: bitmask of set elements + ElementMask getMask() const; + + //@brief : set which elements are toggled + //@param el: bitmask of toggle states + void setMask(ElementMask el); + + PeriodicTablePanel( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxTAB_TRAVERSAL, const wxString& name = wxEmptyString ); + ~PeriodicTablePanel(); + +}; + +/////////////////////////////////////////////////////////////////////////// + +//@brief : get a mask of which elements are toggled +//@return: bitmask of set elements +ElementMask PeriodicTablePanel::getMask() const { + ElementMask msk; + if(118 != m_btns.size()) throw std::logic_error("unexpected number of elements in periodic table panel"); + for(size_t i = 0; i < 118; i++) { + if(m_btns[i]->GetValue()) msk.set(i+1); + } + return msk; +} + +//@brief : set which elements are toggled +//@param el: bitmask of toggle states +void PeriodicTablePanel::setMask(ElementMask el) { + if(118 != m_btns.size()) throw std::logic_error("unexpected number of elements in periodic table panel"); + for(size_t i = 0; i < 118; i++) m_btns[i]->SetValue(el.test(i+1)); +} + +PeriodicTablePanel::PeriodicTablePanel( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name ) : wxPanel( parent, id, pos, size, style, name ) { + wxGridSizer* gSizer = new wxGridSizer( 10, 18, 0, 0 ); + this ->SetMinSize(wxSize(18 * 40, 10 * 50)); + gSizer->SetMinSize(wxSize(18 * 40, 10 * 50)); + + //element to label + std::function labelFunc = [](const size_t z, const bool mu){ + std::ostringstream ss; + if(mu) + ss << "" << ElementMask::Symbol(z) << "\n" << z << ""; + else + ss << ElementMask::Symbol(z) << "\n" << z ;//msw doesn't support multiline markup + return ss.str(); + }; + +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) + const bool markup = false; +#else + const bool markup = true ; +#endif + + //build all buttons up front + for(size_t i = 1; i <= 118; i++) { + std::ostringstream ss; + m_btns.push_back( new wxToggleButton( this, wxID_ANY, labelFunc(i, false), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT) ); + if(markup) m_btns.back()->SetLabelMarkup(labelFunc(i, markup)); + } + + //build special disabled buttons for rare earth breaks + wxToggleButton* reBtn[2] = { + new wxToggleButton( this, wxID_ANY, labelFunc(57, false), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT), + new wxToggleButton( this, wxID_ANY, labelFunc(89, false), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT) + }; + if(markup) reBtn[0]->SetLabelMarkup(labelFunc(57, markup)); + if(markup) reBtn[1]->SetLabelMarkup(labelFunc(89, markup)); + reBtn[0]->Disable(); + reBtn[1]->Disable(); + + //add rows without rare earth breaks + const int brd = 2;//border + for(size_t i = 0; i < 54; i++) { + gSizer->Add( m_btns[i], 1, wxALL|wxEXPAND, brd ); + if(i == 0 ) for(size_t i = 0; i < 16; i++) gSizer->AddStretchSpacer(); + if(i == 3 ) for(size_t i = 0; i < 10; i++) gSizer->AddStretchSpacer(); + if(i == 11) for(size_t i = 0; i < 10; i++) gSizer->AddStretchSpacer(); + } + + //add rows with rare earth breaks + for(size_t j = 0; j < 2; j++) { + size_t start = 54 + j * 32; + for(size_t i = 0; i < 2; i++) gSizer->Add( m_btns[start+i ], 1, wxALL|wxEXPAND, brd );//before break + gSizer->Add( reBtn [ j ], 1, wxALL|wxEXPAND, brd );//break + for(size_t i = 3; i < 18; i++) gSizer->Add( m_btns[start+i+14], 1, wxALL|wxEXPAND, brd );//after break + } + + //add rare earth rows + for(size_t i = 0; i < 18; i++) gSizer->AddStretchSpacer();//empty row + for(size_t j = 0; j < 2; j++) { + size_t start = 54 + j * 32; + for(size_t i = 0; i < 2; i++) gSizer->AddStretchSpacer(); + for(size_t i = 2; i < 17; i++) gSizer->Add( m_btns[start+i], 1, wxALL|wxEXPAND, brd );//after break + gSizer->AddStretchSpacer(); + } + + this->SetSizer( gSizer ); + gSizer->Fit(this); + this->Layout(); +} + +PeriodicTablePanel::~PeriodicTablePanel() { +} + +//////////////////////////////////////////////////////////////////////// +// ElementMask // +//////////////////////////////////////////////////////////////////////// + +#include + +//@brief : get the symbol for an element +//@param z: atomic number +//@return : e.g. "Li" for 3 +std::string ElementMask::Symbol(const size_t z) { + static const std::vector syms = { + "H" , "He", "Li", "Be", "B" , "C" , "N" , "O" , "F" , "Ne", "Na", "Mg", "Al", "Si", "P" , "S" , + "Cl", "Ar", "K" , "Ca", "Sc", "Ti", "V" , "Cr", "Mn", "Fe", "Co", "Ni", "Cu", "Zn", "Ga", "Ge", + "As", "Se", "Br", "Kr", "Rb", "Sr", "Y" , "Zr", "Nb", "Mo", "Tc", "Ru", "Rh", "Pd", "Ag", "Cd", + "In", "Sn", "Sb", "Te", "I" , "Xe", "Cs", "Ba", "La", "Ce", "Pr", "Nd", "Pm", "Sm", "Eu", "Gd", + "Tb", "Dy", "Ho", "Er", "Tm", "Yb", "Lu", "Hf", "Ta", "W" , "Re", "Os", "Ir", "Pt", "Au", "Hg", + "Tl", "Pb", "Bi", "Po", "At", "Rn", "Fr", "Ra", "Ac", "Th", "Pa", "U" , "Np", "Pu", "Am", "Cm", + "Bk", "Cf", "Es", "Fm", "Md", "No", "Lr", "Rf", "Db", "Sg", "Bh", "Hs", "Mt", "Ds", "Rg", "Cn", + "Nh", "Fl", "Mc", "Lv", "Ts", "Og", + }; + return syms[z-1]; +} + +//@brief : convert to string list of elements +//@return: string representation, e.g. "H, He, Li" +std::string ElementMask::str() const { + std::string s; + for(size_t i = 1; i <= 118; i++) { + if(test(i)) { + if(!s.empty()) s += ','; + s += Symbol(i); + } + } + return s; +} + +#endif//_PER_TAB_PAN_H_ diff --git a/include/wx/RoiSelectionPanel.h b/include/wx/RoiSelectionPanel.h new file mode 100644 index 0000000..a911691 --- /dev/null +++ b/include/wx/RoiSelectionPanel.h @@ -0,0 +1,564 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _ROI_SEL_PANEL_H_ +#define _ROI_SEL_PANEL_H_ + +#include +#include +#include + +#include "idx/roi.h" +#include "wx/ImagePanel.h" + +#include + +//@brief: an event to be emmited when the shape changes +class DrawShapeEvent : public wxCommandEvent { + std::vector< std::pair > const* m_shape; + public: + DrawShapeEvent(wxEventType commandEventType = wxEVT_NULL, int id = 0) : wxCommandEvent(commandEventType, id) {} + void SetShape(const std::vector< std::pair >& shp) {m_shape = &shp;} + std::vector< std::pair > const* GetShape() {return m_shape;} + virtual wxEvent *Clone() const { return new DrawShapeEvent(*this); } +}; +wxDEFINE_EVENT(DRAW_SHAPE, DrawShapeEvent); +typedef void (wxEvtHandler::*DrawShapeEventFunction)(DrawShapeEvent&); +#define DrawShapeEventHandler(func) wxEVENT_HANDLER_CAST(DrawShapeEventFunction, func) +#define EVT_DRAW_SHAPE(id, func) wx__DECLARE_EVT1(DRAW_SHAPE, id, DrawShapeEventHandler(func)) + +//@brief: a panel to display + manipulate a region of interest +class ImageRoiEditor : public wxImagePanel { + emsphinx::RoiSelection sel ; + std::vector alphaMask ; + int stroke ; + int cpRd ;//capture radius for nearby point determination + wxPoint lstMouse ;//location of last mouse movement for click+drag + wxColor penClr ; + + //@brief: update mask + refresh + void updateMask(); + + //@brief: emit a shape changed event + void emitShape(); + + // event handling + void leftClick (wxMouseEvent& event);//start new shape, move existing handle, or click + drag shape for outside shape, inside shape, or on handle; add new handle if double click on handle + void rightClick (wxMouseEvent& event);//finish polygon or remove polygon node + void mouseMoved (wxMouseEvent& event);//move shape or handle, update cursor for user hint near handles + void mouseReleased(wxMouseEvent& event);//stop modifying + void keyPressed (wxKeyEvent & event);//fix aspect ratio/line direction while shift is held, delete nodes with backspace/escape + void keyReleased (wxKeyEvent & event);//clear shift held + void onSize (wxSizeEvent & event);//resize image + selection then refresh + void paintEvent (wxPaintEvent& event);//render + + DECLARE_EVENT_TABLE() + + public: + + //@brief : constructor + //@param parent: parent frame + ImageRoiEditor(wxWindow* parent, wxWindowID id=wxID_ANY) : wxImagePanel(parent, id), stroke(2), cpRd(4), lstMouse(-1, -1) {this->SetBackgroundStyle(wxBG_STYLE_PAINT);} + + //@brief : update the image to draw + //@param im: new image + void setImage(wxImage& im) {wxImagePanel::setImage(im); updateMask();} + + //@brief : change the drawing mode + //@param dm: new drawing mode + void setDrawMode(const emsphinx::DrawMode dm) {if(sel.changeMode(dm)) {updateMask();}} + + //@brief : set if the selected area is included or excluded + //@param inv: true/false to select inside/outside shape + void setInvert(const bool inv) {sel.setInv(inv); if(!alphaMask.empty()) {updateMask();}} + + //@brief : set the ROI outline line color + //@param clr: color to draw lines with + void setColor(wxColor clr) {penClr = clr; Refresh();} + + //@brief : set the outline thickness + //@param stk: thickness + void setStroke(int stk) {stroke = stk; cpRd = 3 * stk / 2; Refresh();} + + //@brief : render the drawing + //@param dc: context to render onto + void render(wxDC& dc); + + //@brief : update coordinates of a point + //@param idx: point to update + //@param x : new x coordiante + //@param y : new y coordiante + void movePoint(const size_t idx, const int x, const int y) {sel.movePoint(idx, x, y); updateMask();} + + //@brief : get ROI + //@return: roi + emsphinx::RoiSelection getRoi() const {return sel;} + + //@brief : set ROI + //@param roi: roi + void setRoi(emsphinx::RoiSelection& roi) {sel = roi; updateMask(); Refresh();} +}; + +BEGIN_EVENT_TABLE(ImageRoiEditor, wxPanel) + EVT_LEFT_DOWN (ImageRoiEditor::leftClick ) + EVT_LEFT_DCLICK(ImageRoiEditor::leftClick ) + EVT_RIGHT_DOWN (ImageRoiEditor::rightClick ) + EVT_MOTION (ImageRoiEditor::mouseMoved ) + EVT_LEFT_UP (ImageRoiEditor::mouseReleased) + EVT_KEY_DOWN (ImageRoiEditor::keyPressed ) + EVT_KEY_UP (ImageRoiEditor::keyReleased ) + EVT_SIZE (ImageRoiEditor::onSize ) + EVT_PAINT (ImageRoiEditor::paintEvent ) +END_EVENT_TABLE() + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +enum { + ID_CHOICE = 1000, + ID_COLOR , + ID_SPIN , + ID_CHECK , + ID_DRAW , +}; + +//@brief: panel to hold ImageRoiEditorl with additional controls +class RoiSelectionPanel : public wxPanel { + + wxGrid * grid ; + ImageRoiEditor * drawPane; + wxChoice * choice ; + wxColourPickerCtrl* clrPick ; + wxSpinCtrl * spinCtrl; + wxCheckBox * checkBox; + + std::vector< std::pair > lastShape; + + // event handling + void choiceChanged( wxCommandEvent & event ); + void colorChanged ( wxColourPickerEvent& event ) {drawPane->setColor (event .GetColour ());} + void spinChanged ( wxSpinEvent & event ) {drawPane->setStroke(event .GetPosition());} + void invToggled ( wxCommandEvent & event ) {drawPane->setInvert(checkBox->GetValue ());} + void shapeChanged ( DrawShapeEvent & event ); + void gridChanged ( wxGridEvent & event ); + + public: + RoiSelectionPanel( wxWindow* parent ); + + void setImage(wxImage& im); + + //@brief : get ROI + //@return: roi + emsphinx::RoiSelection getRoi() const {return drawPane->getRoi();} + + //@brief : set ROI + //@param roi: roi + void setRoi(emsphinx::RoiSelection& roi) {checkBox->SetValue(roi.getInv()); drawPane->setRoi(roi);} + + DECLARE_EVENT_TABLE() +}; + +BEGIN_EVENT_TABLE(RoiSelectionPanel, wxPanel) + EVT_CHOICE (ID_CHOICE, RoiSelectionPanel::choiceChanged) + EVT_COLOURPICKER_CHANGED(ID_COLOR , RoiSelectionPanel::colorChanged ) + EVT_SPINCTRL (ID_SPIN , RoiSelectionPanel::spinChanged ) + EVT_CHECKBOX (ID_CHECK , RoiSelectionPanel::invToggled ) + EVT_DRAW_SHAPE (ID_DRAW , RoiSelectionPanel::shapeChanged ) + EVT_GRID_CELL_CHANGED ( RoiSelectionPanel::gridChanged ) +END_EVENT_TABLE() + +/////////////////////////////////////////////////////////////////////// +// ImageRoiEditor // +/////////////////////////////////////////////////////////////////////// + +//@brief: update mask + refresh +void ImageRoiEditor::updateMask() { + //build mask + if(wxImagePanel::hasImage()) { + const size_t w = wxImagePanel::GetSize().GetWidth (); + const size_t h = wxImagePanel::GetSize().GetHeight(); + alphaMask = sel.buildMask(w, h); + if(!sel.hasShape()) std::fill(alphaMask.begin(), alphaMask.end(), sel.getInv() ? 0 : 1); + for(size_t j = 0; j < h; j++) { + for(size_t i = 0; i < w; i++) { + bool in = alphaMask[j*w+i] == 1; + wxImagePanel::SetAlpha(i, j, in ? 0xFF : 0x40); + } + } + } else { + alphaMask.clear(); + } + emitShape(); + Refresh(); +} + +//@brief: emit a shape changed event +void ImageRoiEditor::emitShape() { + DrawShapeEvent event(DRAW_SHAPE, GetId()); + event.SetEventObject(this); + event.SetShape(sel.getPts()); + ProcessWindowEvent(event);//send +} + +void ImageRoiEditor::leftClick(wxMouseEvent& event) { + wxPoint xy(event.GetX(), event.GetY()); + wxImagePanel::dc2im(xy.x, xy.y); + if(sel.hasSelection()) {//not currently manipulating an existing point + if(sel.trySelect(xy.x, xy.y, (int)std::round(1.0 / wxImagePanel::getScl2() * cpRd * cpRd))) { + //did click on an existing point + switch(sel.getMode()) { + case emsphinx::DrawMode::Rectangle: + case emsphinx::DrawMode::Ellipse : break;//we found it, we're done + case emsphinx::DrawMode::Polygon : + if(event.LeftDClick()) { + sel.dupPolyPt((int)std::round(2.0 / wxImagePanel::getScl() * cpRd));//duplicate selected point + updateMask(); + } else { + //just selecting point was enough + } + break; + sel.startPoly(xy.x, xy.y); updateMask(); break; + } + } else if(sel.inside(xy.x, xy.y) != sel.getInv()) { + lstMouse = wxPoint(xy.x, xy.y); + } else {//user didn't try click on an existing point or inside existing shape, start a new shape + switch(sel.getMode()) { + case emsphinx::DrawMode::Rectangle: + case emsphinx::DrawMode::Ellipse : sel.startRect(xy.x, xy.y); updateMask(); break; + case emsphinx::DrawMode::Polygon : sel.startPoly(xy.x, xy.y); updateMask(); break; + } + Refresh(); + } + this->SetCursor(*wxCROSS_CURSOR);//use cross during drawing + } else {//currently manipulating an existing point + switch(sel.getMode()) { + case emsphinx::DrawMode::Rectangle: + case emsphinx::DrawMode::Ellipse : break; + case emsphinx::DrawMode::Polygon : if(sel.addPolyPt(xy.x, xy.y, (int)std::round(1.0 / wxImagePanel::getScl2() * cpRd * cpRd)) ) updateMask(); break; + } + } + event.Skip(); +} + +void ImageRoiEditor::rightClick(wxMouseEvent& event) { + wxPoint xy(event.GetX(), event.GetY()); + wxImagePanel::dc2im(xy.x, xy.y); + switch(sel.getMode()) { + case emsphinx::DrawMode::Rectangle: + case emsphinx::DrawMode::Ellipse : break; + case emsphinx::DrawMode::Polygon : + if(sel.buildingPoly()) { + sel.addPolyPt(xy.x, xy.y, cpRd * cpRd);//add the current point + sel.finishPoly();//close polygon + updateMask();//update mask + // this->SetCursor(*wxSTANDARD_CURSOR);//switch cursor to indicate close + } else if(sel.trySelect(xy.x, xy.y, (int)std::round(1.0 / wxImagePanel::getScl2() * cpRd * cpRd)) ) { + sel.remPolyPt(); + updateMask(); + } + break; + } + event.Skip(); +} + +void ImageRoiEditor::mouseMoved(wxMouseEvent& event) { + wxPoint xy(event.GetX(), event.GetY()); + wxImagePanel::dc2im(xy.x, xy.y); + if(-1 != lstMouse.x && -1 != lstMouse.y) {//clicking + dragging + sel.translatePoints(xy.x - lstMouse.x, xy.y - lstMouse.y);//drag selection + lstMouse.x = xy.x;//update most recent x + lstMouse.y = xy.y;//update most recent y + updateMask(); + } else { + switch(sel.getMode()) { + case emsphinx::DrawMode::Rectangle: + case emsphinx::DrawMode::Ellipse : if(sel.resizeRect(xy.x, xy.y)) updateMask(); break; + case emsphinx::DrawMode::Polygon : if(sel.resizePoly(xy.x, xy.y)) updateMask(); break; + } + if(sel.hasSelection()) {//we're not currently building/manipulating + this->SetCursor(-1 == sel.nearPt(xy.x, xy.y, (int)std::round(1.0 / wxImagePanel::getScl2() * cpRd * cpRd)) ? *wxSTANDARD_CURSOR : *wxCROSS_CURSOR);//use cross to indicate near enough to grab handle + } else if (emsphinx::DrawMode::Polygon == sel.getMode() && sel.buildingPoly()) {//handle in construction polygon specially + std::pair pt = sel.getPts().front(); + wxImagePanel::im2dc(pt.first, pt.second); + wxPoint dlt(event.GetX() - pt.first, event.GetY() - pt.second); + const bool close = dlt.x*dlt.x + dlt.y*dlt.y < cpRd * cpRd; + this->SetCursor(close ? *wxSTANDARD_CURSOR : *wxCROSS_CURSOR);//use regular cursor to indicate near enough to close polygon + } + } + event.Skip(); +} + +void ImageRoiEditor::mouseReleased(wxMouseEvent& event) { + switch(sel.getMode()) { + case emsphinx::DrawMode::Rectangle: + case emsphinx::DrawMode::Ellipse : sel.ungrabRect(); updateMask(); break; + case emsphinx::DrawMode::Polygon : sel.ungrabPoly(); updateMask(); break; + } + this->SetCursor(sel.hasSelection() ? *wxSTANDARD_CURSOR : *wxCROSS_CURSOR);//switch to regular cursor when done editing + lstMouse = wxPoint(-1, -1); + event.Skip(); +} + +void ImageRoiEditor::keyPressed(wxKeyEvent& event) { + switch(event.GetKeyCode()) { + case WXK_DELETE: + case WXK_BACK : + switch(sel.getMode()) { + case emsphinx::DrawMode::Rectangle: + case emsphinx::DrawMode::Ellipse : break; + case emsphinx::DrawMode::Polygon : if(sel.remPolyPt()) {emitShape(); Refresh();} break; + } + break; + + case WXK_ESCAPE: if(sel.clear()) updateMask(); break; + + case WXK_SHIFT : sel.setFixedAspect(true); emitShape(); event.Skip(); break; + + default: event.Skip(); + } +} + +void ImageRoiEditor::keyReleased(wxKeyEvent& event) { + switch(event.GetKeyCode()) { + case WXK_SHIFT: sel.setFixedAspect(false); emitShape(); event.Skip(); break; + + default: event.Skip(); + } +} + +//@brief : event for image resizing (refresh panel) +//@param event: resize event +void ImageRoiEditor::onSize(wxSizeEvent& event) { + lstMouse = wxPoint(-1, -1);//invalidate click+drag + Refresh(); + // event.Skip();//skip the event. +} + +void ImageRoiEditor::paintEvent(wxPaintEvent & event) { + wxAutoBufferedPaintDC dc(this); + render(dc); +} + +void ImageRoiEditor::render(wxDC& dc) { + //scale points + std::vector pts; + for(const std::pair& p : sel.getPts()) { + int x = p.first ; + int y = p.second; + wxImagePanel::im2dc(x, y); + pts.push_back(wxPoint(x, y)); + } + + //draw based on mode + dc.Clear(); + wxImagePanel::render(dc);//draw the image first + dc.SetBrush( *wxTRANSPARENT_BRUSH ); + dc.SetPen( wxPen(penClr , stroke) ); + switch(sel.getMode()) { + case emsphinx::DrawMode::Rectangle: + case emsphinx::DrawMode::Ellipse : { + if(!pts.empty()) { + //draw rectangle + const int x0 = std::min(pts.front().x, pts.back().x); + const int y0 = std::min(pts.front().y, pts.back().y); + const int w = std::max(pts.front().x, pts.back().x) + 1 - x0; + const int h = std::max(pts.front().y, pts.back().y) + 1 - y0; + if(emsphinx::DrawMode::Rectangle == sel.getMode() || 0 == w || 0 == h)//gtk doesn't like drawing empty ellipses + dc.DrawRectangle( x0, y0, w, h ); + else + dc.DrawEllipse ( x0, y0, w, h ); + } + } break; + + case emsphinx::DrawMode::Polygon : + if(!pts.empty()) dc.DrawLines(pts.size(), pts.data()); + break; + } + + //draw handles + dc.SetBrush( *wxWHITE_BRUSH ); + dc.SetPen( *wxBLACK_PEN ); + for(const auto& pt : pts) dc.DrawCircle(pt, cpRd); +} + +/////////////////////////////////////////////////////////////////////// +// RoiSelectionPanel // +/////////////////////////////////////////////////////////////////////// + +void RoiSelectionPanel::choiceChanged( wxCommandEvent& event ) { + switch(choice->GetSelection()) { + case 0: drawPane->setDrawMode(emsphinx::DrawMode::Rectangle); break; + case 1: drawPane->setDrawMode(emsphinx::DrawMode::Ellipse ); break; + case 2: drawPane->setDrawMode(emsphinx::DrawMode::Polygon ); break; + } + event.Skip(); +} + +void RoiSelectionPanel::shapeChanged ( DrawShapeEvent & event ) { + //first resize grid if needed + size_t numRow = grid->GetNumberRows();//current grid rows + size_t tarRow = event.GetShape()->size();//target grid rows + if(tarRow > 3) {//only possible for polygons + if(event.GetShape()->front() == event.GetShape()->back()) --tarRow;//dont display duplicate first/last point + } + if(tarRow < numRow) { + grid->DeleteRows(tarRow, numRow - tarRow); + } else if(tarRow > numRow) { + grid->AppendRows(tarRow - numRow); + } + + //next update grid entries / saved shape + lastShape.resize(tarRow, std::pair(-1,-1)); + for(size_t i = 0; i < lastShape.size(); i++) { + if(lastShape[i].first != event.GetShape()->at(i).first ) { + lastShape[i].first = event.GetShape()->at(i).first; + std::ostringstream ss; + ss << lastShape[i].first ; + grid->SetCellValue(i, 0, ss.str().c_str()); + } + if(lastShape[i].second != event.GetShape()->at(i).second) { + lastShape[i].second = event.GetShape()->at(i).second; + std::ostringstream ss; + ss << lastShape[i].second; + grid->SetCellValue(i, 1, ss.str().c_str()); + + } + + } +} + +void RoiSelectionPanel::gridChanged ( wxGridEvent & event ) { + //get row + int idx = event.GetRow(); + if(0 == event.GetCol()) + lastShape[idx].first = atoi(grid->GetCellValue(idx, 0)); + else + lastShape[idx].second = atoi(grid->GetCellValue(idx, 1)); + drawPane->movePoint(idx, lastShape[idx].first, lastShape[idx].second); +} + +void RoiSelectionPanel::setImage(wxImage& im) { + //get the minimum size of the panel (from contrl bar) and update minimum height to reflect images + const wxSize& sz = this->GetSizer()->GetMinSize(); + double numIm = double(sz.GetWidth() - grid->GetSize().GetWidth()) / im.GetSize().GetWidth();//this is how many times we could fit the full image + int imH = std::round(numIm * im.GetSize().GetHeight());//this is how tall the images should be for the widhts to nicely fill the frame + this->GetSizer()->SetMinSize(wxSize(sz.GetWidth(), sz.GetHeight() + imH));//make images fill the frame + this->SetMinSize(wxSize(sz.GetWidth(), sz.GetHeight() + imH));//make images fill the frame + this->GetSizer()->Fit(GetParent());//force the parent window to grow to accomodate our new size + + //actually update the image + drawPane->setImage(im); +} + +RoiSelectionPanel::RoiSelectionPanel( wxWindow* parent ) : wxPanel(parent) { + + //build sizers + wxBoxSizer* vSizer = new wxBoxSizer(wxVERTICAL ); + wxBoxSizer* drwSizer = new wxBoxSizer(wxHORIZONTAL); + wxBoxSizer* cntrSizer = new wxBoxSizer(wxHORIZONTAL); + + //build draw elements + drawPane = new ImageRoiEditor ( this, ID_DRAW ); + + grid = new wxGrid( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); + + // Grid + grid->CreateGrid ( 0, 2 ); + // grid->EnableEditing ( false ); + grid->EnableGridLines ( true ); + grid->EnableDragGridSize( false ); + grid->SetMargins ( 0, 0 ); + grid->SetTabBehaviour ( wxGrid::TabBehaviour::Tab_Wrap ); + + // rows/columns + grid->EnableDragColMove( false ); + grid->SetColLabelSize( 30 ); + grid->SetRowLabelSize( 30 ); + grid->SetColLabelAlignment( wxALIGN_CENTER, wxALIGN_CENTER ); + grid->SetRowLabelAlignment( wxALIGN_CENTER, wxALIGN_CENTER ); + grid->SetColLabelValue( 0, wxT("X") ); + grid->SetColLabelValue( 1, wxT("Y") ); + + // Label Appearance + + // Cell Defaults + grid->SetDefaultCellAlignment( wxALIGN_LEFT, wxALIGN_TOP ); + grid->SetDefaultEditor(new wxGridCellNumberEditor()); + + + //build control elements + wxString choiceStrs[] = {"Rectangle", "Ellipse", "Polygon" }; + choice = new wxChoice ( this, ID_CHOICE, wxDefaultPosition, wxDefaultSize, 3, choiceStrs, 0 ); + clrPick = new wxColourPickerCtrl( this, ID_COLOR , *wxRED); + checkBox = new wxCheckBox ( this, ID_CHECK , wxT("Inverted")); + wxStaticText* txt = new wxStaticText ( this, wxID_ANY , wxT("Stroke") , wxDefaultPosition, wxDefaultSize, 0 );//spinner label + spinCtrl = new wxSpinCtrl ( this, ID_SPIN , wxEmptyString , wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 16, 3); + + //set some defaults + drawPane->setDrawMode(emsphinx::DrawMode::Rectangle); + drawPane->setColor(clrPick->GetColour()); + drawPane->setStroke(spinCtrl->GetValue()); + drawPane->setInvert (checkBox->GetValue()); + choice->SetSelection( 0 ); + + //build the draw pane + drwSizer->Add(grid , 0, wxALL|wxEXPAND, 5 ); + drwSizer->Add(drawPane, 1, wxEXPAND, 0 ); + + //build the controls + cntrSizer->Add(choice , 0, wxALL|wxALIGN_CENTER_VERTICAL, 5); + cntrSizer->Add(checkBox, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5); + cntrSizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL), 0, wxALL|wxEXPAND, 5); + cntrSizer->Add(clrPick , 0, wxALL|wxALIGN_CENTER_VERTICAL, 5); + cntrSizer->Add(txt , 0, wxALL|wxALIGN_CENTER_VERTICAL, 5); + cntrSizer->Add(spinCtrl, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5); + + //and draw + control sizers to main sizer + vSizer->Add(drwSizer , 1, wxEXPAND , 0 ); + vSizer->Add(cntrSizer, 0, wxALL|wxALIGN_CENTER_HORIZONTAL, 5 ); + + //layout frame + this->SetSizer(vSizer); + this->SetAutoLayout(true); +} + +#endif//_ROI_SEL_PANEL_H_ diff --git a/include/wx/ScanDimsPanel.h b/include/wx/ScanDimsPanel.h new file mode 100644 index 0000000..7db3f10 --- /dev/null +++ b/include/wx/ScanDimsPanel.h @@ -0,0 +1,404 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _SCAN_DIMS_PAN_H_ +#define _SCAN_DIMS_PAN_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wx/ValidityPanel.h" + +#include "modality/ebsd/pattern.hpp" +#include "idx/roi.h" + +/////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +/// Class ScanDimsPanel +/////////////////////////////////////////////////////////////////////////////// +class ScanDimsPanel : public ValidityPanel +{ + private: + int m_numPat = -1; + std::shared_ptr m_pat;;//can be null + std::shared_ptr< std::vector > m_iq, m_ci;//existing maps (can be NULL) + + emsphinx::RoiSelection m_roi;//roi + std::vector m_mask;//current roi mask + + wxIntegerValidator valPix;//scan size + wxFloatingPointValidator valUm ;//pixel size + + void updateChoices(); + + protected: + wxTextCtrl* m_txtW ; + wxTextCtrl* m_txtH ; + wxTextCtrl* m_txtX ; + wxTextCtrl* m_txtY ; + wxChoice * m_cmbRoiIm; + wxButton * m_btnRoi ; + wxTextCtrl* m_txtCvg ; + wxButton * m_btnClear; + wxCheckBox* m_chkDims ; + + // Virtual event handlers, overide them in your derived class + virtual void ScanDimsChanged( wxCommandEvent& event ) { testValid(); } + virtual void ScanStepChanged( wxCommandEvent& event ) { testValid(); } + virtual void DoROI ( wxCommandEvent& event ); + virtual void ClearROI ( wxCommandEvent& event ) { m_txtCvg->SetValue("No ROI"); m_btnClear->Enable(false); m_roi.clear(); } + virtual void OnCheckbox ( wxCommandEvent& event ) { testValid(); } + + public: + + ScanDimsPanel( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 350,300 ), long style = wxTAB_TRAVERSAL, const wxString& name = wxEmptyString ); + ~ScanDimsPanel(); + + //@brief : get the scan width + //@return: scan width in pixels + int getW() const {long v; m_txtW->GetLineText(0).ToLong(&v); return v;} + + //@brief : get the scan height + //@return: scan height in pixels + int getH() const {long v; m_txtH->GetLineText(0).ToLong(&v); return v;} + + //@brief : get the pixel width + //@return: pixel width in microns + double getX() const {double v; m_txtX->GetLineText(0).ToDouble(&v); return v;} + + //@brief : get the pixel width + //@return: pixel width in microns + double getY() const {double v; m_txtY->GetLineText(0).ToDouble(&v); return v;} + + //@brief : set the scan width + //@param w: scan width in pixels + void setW(const int w) {m_txtW->Clear(); m_txtW->operator<<(w);} + + //@brief : set the scan height + //@param h: scan height in pixels + void setH(const int h) {m_txtH->Clear(); m_txtH->operator<<(h);} + + //@brief : set the pixel width + //@param y: pixel width in microns + void setX(const float x) {m_txtX->Clear(); m_txtX->operator<<(x);} + + //@brief : set the pixel width + //@param z: pixel width in microns + void setY(const float y) {m_txtY->Clear(); m_txtY->operator<<(y);} + + //@brief : set pattern file (to compute # patterns and do preview) + //@param pat: shared pointer to pattern file + void setPats(std::shared_ptr pats) {m_pat = pats; updateChoices();} + + //@brief : set existing maps (for preview) and # patterns + //@param iq : iq map + //@param ci : ci map + //@param num: number of patterns + void setMaps(std::shared_ptr< std::vector > iq, std::shared_ptr< std::vector > ci, int num) {m_iq = iq; m_ci = ci; m_numPat = num; updateChoices();} + + //@brief : get the selected map (or empty pointer for no selection) + //@return: currently selected image + wxImage getMap() const; + + //@brief : get the ROI + //@return: roi + emsphinx::RoiSelection getRoi() const {return m_roi;} + + //@brief: sanity check the current state + //@return: true if the values parsed from the panel are reasonable, false otherwise + //@note : checks for has a file, has detector sizes, and has an AHE value + std::string validMsg() const { + if(m_txtW->GetLineText(0).IsEmpty()) return "scan width empty"; + if(m_txtH->GetLineText(0).IsEmpty()) return "scan height empty"; + if(m_txtX->GetLineText(0).IsEmpty()) return "x step empty"; + if(m_txtY->GetLineText(0).IsEmpty()) return "y step empty"; + int numPix = getW() * getH(); + std::ostringstream ss; + ss << m_numPat << " patterns for " << numPix << " pixels"; + if(numPix > m_numPat) return "not enough patterns (" + ss.str() + ')'; + if(numPix < m_numPat && !m_chkDims->IsChecked()) return "too many patterns (" + ss.str() + ')'; + return ""; + } + +}; + +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version Oct 26 2018) +// http://www.wxformbuilder.org/ +// +// PLEASE DO *NOT* EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// + + +#include "SinglePanelDialog.h" +#include "RoiSelectionPanel.h" + +void ScanDimsPanel::updateChoices() { + //accumluate choices + int numChoice = 0; + int prefChoice = 0; + wxString choices[3]; +/* +TODO: enable this in DoROI + if(NULL != m_pat.get()) { + prefChoice = numChoice; + choices[numChoice++] = wxT("Compute IQ"); + } +*/ + if(NULL == m_ci.get() ? false : !m_ci->empty()) { + prefChoice = numChoice;//prefer existing CI of computing IQ (faster) + choices[numChoice++] = wxT("Existing CI"); + } + if(NULL == m_iq.get() ? false : !m_iq->empty()) { + prefChoice = numChoice;//prefer existing IQ over existing CI (nicer image) + choices[numChoice++] = wxT("Existing IQ"); + } + + //reverse choice order for box + std::reverse(choices, choices + numChoice); + prefChoice = numChoice - 1 - prefChoice; + + //now build box and set choice + m_cmbRoiIm->Clear(); + for(int i = 0; i < numChoice; i++) m_cmbRoiIm->Append(choices[i]); + m_cmbRoiIm->SetSelection(prefChoice); + m_btnRoi->Enable(numChoice > 0); +} + +//@brief : get the selected map (or empty pointer for no selection) +//@return: currently selected image +wxImage ScanDimsPanel::getMap() const { + //get float image + std::shared_ptr< std::vector > ptr; + wxString sel = m_cmbRoiIm->GetString(m_cmbRoiIm->GetSelection()); + if (wxString("Existing CI") == sel) ptr = m_ci; + else if(wxString("Existing IQ") == sel) ptr = m_iq; + + //convert to 8 bit (this needs to be moved to another file) + std::vector buff(m_numPat); + if(NULL != ptr.get()) { + std::pair minMax = std::minmax_element(ptr->data(), ptr->data() + buff.size());//compute minimum and maximum quality + const float scale = 255.0f / (*minMax.second - *minMax.first); + std::transform(ptr->data(), ptr->data() + buff.size(), (uint8_t*)buff.data(), [&](const float& v){return (uint8_t)std::round(scale * (v - (*minMax.first)));}); + } + + //wrap as image (likewise) + size_t w = getW(); + size_t h = getH(); + wxImage im(getW(), getH()); + unsigned char* alpha = (unsigned char*)malloc(buff.size());//SetAlpha requires this... + std::fill(alpha, alpha + buff.size(), 0xFF); + im.SetAlpha(alpha, false);//false -> image takes ownership and will free + for(size_t j = 0; j < h; j++) { + for(size_t i = 0; i < w; i++) { + char c = buff[j * w + i]; + im.SetRGB(i, j, c, c, c); + } + } + return im; +} + +void ScanDimsPanel::DoROI( wxCommandEvent& event ) { + SinglePanelDialog dlg(this, wxID_ANY, "Select ROI"); + wxImage im = getMap(); + dlg.getPanel()->setImage(im); + dlg.getPanel()->setRoi(m_roi); + if(wxID_OK == dlg.ShowModal()) {//this currently crashes... + m_roi = dlg.getPanel()->getRoi(); + std::vector mask = m_roi.buildMask(getW(), getH()); + size_t num0 = std::count_if(mask.begin(), mask.end(), [](const char&c){return c == 0;}); + double frac = 1.0 - double(num0) / (mask.size()); + m_txtCvg->ChangeValue(wxString::Format(wxT("%0.1f"), frac * 100)); + m_btnClear->Enable(true); + } +} + +ScanDimsPanel::ScanDimsPanel( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name ) : ValidityPanel( parent, id, pos, size, style, name ) { + //split panel into 2 vertical boxes + wxBoxSizer * bScnDm = new wxBoxSizer( wxVERTICAL ); + wxStaticBoxSizer* sbScnDim = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, wxT("Scan Dimensions" ) ), wxVERTICAL ); + wxStaticBoxSizer* sbRoi = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, wxT("Region of Interest") ), wxVERTICAL ); + bScnDm->Add( sbScnDim, 2, wxEXPAND, 5 ); + bScnDm->Add( sbRoi , 1, wxEXPAND, 5 ); + + //top pox is 6x4 grid + wxFlexGridSizer* fgScnDim = new wxFlexGridSizer( 4, 6, 0, 0 ); + fgScnDim->AddGrowableCol( 0 ); fgScnDim->AddGrowableCol( 2 ); fgScnDim->AddGrowableCol( 5 ); + fgScnDim->AddGrowableRow( 0 ); fgScnDim->AddGrowableRow( 1 ); fgScnDim->AddGrowableRow( 2 ); fgScnDim->AddGrowableRow( 3 ); + fgScnDim->SetFlexibleDirection( wxHORIZONTAL ); + fgScnDim->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); + sbScnDim->Add( fgScnDim, 1, wxEXPAND, 5 ); + + //bottom box is 6x2 grid + wxFlexGridSizer* fgRoi = new wxFlexGridSizer( 2, 6, 0, 0 ); + fgRoi->AddGrowableCol( 0 ); fgRoi->AddGrowableCol( 2 ); fgRoi->AddGrowableCol( 5 ); + fgRoi->AddGrowableRow( 0 ); fgRoi->AddGrowableRow( 1 ); + fgRoi->SetFlexibleDirection( wxHORIZONTAL ); + fgRoi->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); + sbRoi->Add( fgRoi, 1, wxEXPAND, 5 ); + + //sub grid for # and clear ubbton + wxFlexGridSizer* fgpctClr = new wxFlexGridSizer( 1, 2, 0, 0 ); + fgpctClr->AddGrowableCol( 0 ); fgpctClr->AddGrowableCol( 1 ); + fgpctClr->AddGrowableRow( 0 ); + fgpctClr->SetFlexibleDirection( wxBOTH ); + fgpctClr->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); + + //build all labels + wxStaticText* staTxtScnW = new wxStaticText( sbScnDim->GetStaticBox(), wxID_ANY, wxT("Scan Width" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtScnW ->Wrap(-1); + wxStaticText* staTxtPixW = new wxStaticText( sbScnDim->GetStaticBox(), wxID_ANY, wxT("pix" ), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT ); staTxtPixW ->Wrap(-1); + wxStaticText* staTxtScnH = new wxStaticText( sbScnDim->GetStaticBox(), wxID_ANY, wxT("Scan Height"), wxDefaultPosition, wxDefaultSize, 0 ); staTxtScnH ->Wrap(-1); + wxStaticText* staTxtPixH = new wxStaticText( sbScnDim->GetStaticBox(), wxID_ANY, wxT("pix" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtPixH ->Wrap(-1); + wxStaticText* staTxtStpX = new wxStaticText( sbScnDim->GetStaticBox(), wxID_ANY, wxT("X Step" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtStpX ->Wrap(-1); + wxStaticText* staTxtUmX = new wxStaticText( sbScnDim->GetStaticBox(), wxID_ANY, wxT("um" ), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT ); staTxtUmX ->Wrap(-1); + wxStaticText* staTxtScnStepY = new wxStaticText( sbScnDim->GetStaticBox(), wxID_ANY, wxT("Y Step" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtScnStepY->Wrap(-1); + wxStaticText* staTxtUmY = new wxStaticText( sbScnDim->GetStaticBox(), wxID_ANY, wxT("um" ), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT ); staTxtUmY ->Wrap(-1); + wxStaticText* staTxtIm = new wxStaticText( sbRoi ->GetStaticBox(), wxID_ANY, wxT("Image" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtIm ->Wrap(-1); + wxStaticText* staTxtCvg = new wxStaticText( sbRoi ->GetStaticBox(), wxID_ANY, wxT("Coverage" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtCvg ->Wrap(-1); + wxStaticText* staTxtCvgU = new wxStaticText( sbRoi ->GetStaticBox(), wxID_ANY, wxT("%" ), wxDefaultPosition, wxDefaultSize, 0 ); staTxtCvgU ->Wrap(-1); + + //set text validator ranges/precisions + valPix.SetRange( 1 , 5000 ); + valUm .SetRange( 0.01f, 100 ); + valUm.SetPrecision(2);//max 6 for float + + //build elements for scan dimensions box + m_txtW = new wxTextCtrl( sbScnDim->GetStaticBox(), wxID_ANY, wxT("" ), wxDefaultPosition, wxDefaultSize, wxTE_CENTER , valPix ); + m_txtH = new wxTextCtrl( sbScnDim->GetStaticBox(), wxID_ANY, wxT("" ), wxDefaultPosition, wxDefaultSize, wxTE_CENTER , valPix ); + m_txtX = new wxTextCtrl( sbScnDim->GetStaticBox(), wxID_ANY, wxT("" ), wxDefaultPosition, wxDefaultSize, wxTE_CENTER , valUm ); + m_txtY = new wxTextCtrl( sbScnDim->GetStaticBox(), wxID_ANY, wxT("" ), wxDefaultPosition, wxDefaultSize, wxTE_CENTER , valUm ); + m_chkDims = new wxCheckBox( sbScnDim->GetStaticBox(), wxID_ANY, wxT("Ignore Extra Patterns"), wxDefaultPosition, wxDefaultSize, 0 ); + sbScnDim->Add( m_chkDims, 0, wxALIGN_RIGHT, 5 ); + + //build elements for ROI box + wxString m_cmbRoiImChoices[] = { wxT("Compute IQ"), wxT("Existing IQ"), wxT("Existing CI") }; + int m_cmbRoiImNChoices = sizeof( m_cmbRoiImChoices ) / sizeof( wxString ); + m_cmbRoiIm = new wxChoice ( sbRoi ->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 , m_cmbRoiImChoices, 0 ); + m_btnRoi = new wxButton ( sbRoi ->GetStaticBox(), wxID_ANY, wxT("Select ROI..."), wxDefaultPosition, wxDefaultSize, 0 ); + m_txtCvg = new wxTextCtrl( sbRoi ->GetStaticBox(), wxID_ANY, wxT("No ROI" ), wxDefaultPosition, wxDefaultSize, wxTE_CENTER|wxTE_READONLY ); + m_btnClear = new wxButton ( sbRoi ->GetStaticBox(), wxID_ANY, wxT("Clear ROI" ), wxDefaultPosition, wxDefaultSize, 0 ); + m_cmbRoiIm->SetSelection( -1 ); + + //assemble scan dims grid + fgScnDim->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgScnDim->Add( staTxtScnW , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgScnDim->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgScnDim->Add( m_txtW , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT , 5 ); + fgScnDim->Add( staTxtPixW , 0, wxALL|wxALIGN_CENTER_VERTICAL , 5 ); + fgScnDim->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgScnDim->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgScnDim->Add( staTxtScnH , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgScnDim->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgScnDim->Add( m_txtH , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT , 5 ); + fgScnDim->Add( staTxtPixH , 0, wxALL|wxALIGN_CENTER_VERTICAL , 5 ); + fgScnDim->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgScnDim->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgScnDim->Add( staTxtStpX , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgScnDim->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgScnDim->Add( m_txtX , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT , 5 ); + fgScnDim->Add( staTxtUmX , 0, wxALL|wxALIGN_CENTER_VERTICAL , 5 ); + fgScnDim->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgScnDim->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgScnDim->Add( staTxtScnStepY, 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgScnDim->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgScnDim->Add( m_txtY , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT , 5 ); + fgScnDim->Add( staTxtUmY , 0, wxALL|wxALIGN_CENTER_VERTICAL , 5 ); + fgScnDim->Add( 0 , 0, 1 , wxEXPAND , 5 ); + + //assemble %/clear roi subgrid + fgpctClr->Add( staTxtCvgU , 0, wxALL|wxALIGN_CENTER_VERTICAL , 5 ); + fgpctClr->Add( m_btnClear , 0, wxALL|wxALIGN_CENTER_VERTICAL , 5 ); + + //assemble roi grid + fgRoi ->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgRoi ->Add( staTxtIm , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgRoi ->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgRoi ->Add( m_cmbRoiIm , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxEXPAND , 5 ); + fgRoi ->Add( m_btnRoi , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxEXPAND , 5 ); + fgRoi ->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgRoi ->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgRoi ->Add( staTxtCvg , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + fgRoi ->Add( 0 , 0, 1 , wxEXPAND , 5 ); + fgRoi ->Add( m_txtCvg , 0, wxALL|wxALIGN_CENTER_VERTICAL|wxEXPAND , 5 ); + fgRoi ->Add( fgpctClr , 1, wxEXPAND , 5 ); + fgRoi ->Add( 0 , 0, 1 , wxEXPAND , 5 ); + + this->SetSizer( bScnDm ); + this->Layout(); + m_btnRoi->Enable(false);//we don't have an image source yet + m_btnClear->Enable(false);//we don't have an ROI yet + + // Connect Events + m_txtW ->Connect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( ScanDimsPanel::ScanDimsChanged ), NULL, this ); + m_txtH ->Connect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( ScanDimsPanel::ScanDimsChanged ), NULL, this ); + m_txtX ->Connect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( ScanDimsPanel::ScanStepChanged ), NULL, this ); + m_txtY ->Connect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( ScanDimsPanel::ScanStepChanged ), NULL, this ); + m_btnRoi ->Connect( wxEVT_COMMAND_BUTTON_CLICKED , wxCommandEventHandler( ScanDimsPanel::DoROI ), NULL, this ); + m_btnClear->Connect( wxEVT_COMMAND_BUTTON_CLICKED , wxCommandEventHandler( ScanDimsPanel::ClearROI ), NULL, this ); + m_chkDims ->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ScanDimsPanel::OnCheckbox ), NULL, this ); +} + +ScanDimsPanel::~ScanDimsPanel() { + // Disconnect Events + m_txtW ->Disconnect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( ScanDimsPanel::ScanDimsChanged ), NULL, this ); + m_txtH ->Disconnect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( ScanDimsPanel::ScanDimsChanged ), NULL, this ); + m_txtX ->Disconnect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( ScanDimsPanel::ScanStepChanged ), NULL, this ); + m_txtY ->Disconnect( wxEVT_COMMAND_TEXT_UPDATED , wxCommandEventHandler( ScanDimsPanel::ScanStepChanged ), NULL, this ); + m_btnRoi ->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED , wxCommandEventHandler( ScanDimsPanel::DoROI ), NULL, this ); + m_btnClear->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED , wxCommandEventHandler( ScanDimsPanel::ClearROI ), NULL, this ); + m_chkDims ->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( ScanDimsPanel::OnCheckbox ), NULL, this ); +} + +#endif//_SCAN_DIMS_PAN_H_ diff --git a/include/wx/SinglePanelDialog.h b/include/wx/SinglePanelDialog.h new file mode 100644 index 0000000..f150dbd --- /dev/null +++ b/include/wx/SinglePanelDialog.h @@ -0,0 +1,110 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _SINGLE_PAN_DLG_H_ +#define _SINGLE_PAN_DLG_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +/// Class SinglePanelDialog +/////////////////////////////////////////////////////////////////////////////// +template +class SinglePanelDialog : public wxDialog { + static_assert(std::is_base_of::value, "SinglePanelDialog must be templated on a type derived from wxPanel"); + TPanel * m_pan ; + wxButton * m_cancel; + wxButton * m_ok ; + + public: + + TPanel* getPanel() {return m_pan;} + + SinglePanelDialog( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxEmptyString, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER ); +}; + +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version Oct 26 2018) +// http://www.wxformbuilder.org/ +// +// PLEASE DO *NOT* EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// +template +SinglePanelDialog::SinglePanelDialog( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style ) { + this->SetSizeHints( wxDefaultSize, wxDefaultSize ); + + wxBoxSizer* bSizer = new wxBoxSizer( wxVERTICAL ); + + m_pan = new TPanel(this); + bSizer->Add( m_pan, 1, wxEXPAND | wxALL, 5 ); + + wxGridSizer* gSizer; + gSizer = new wxGridSizer( 1, 2, 0, 0 ); + + m_cancel = new wxButton( this, wxID_CANCEL, wxT("Cancel"), wxDefaultPosition, wxDefaultSize, 0 ); + gSizer->Add( m_cancel, 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + + m_ok = new wxButton( this, wxID_OK, wxT("OK"), wxDefaultPosition, wxDefaultSize, 0 ); + gSizer->Add( m_ok, 0, wxALL|wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + m_ok->SetFocus(); + m_ok->SetDefault(); + + bSizer->Add( gSizer, 0, wxEXPAND, 5 ); + + this->SetSizer( bSizer ); + bSizer->Fit(this); + this->SetMinSize(bSizer->GetMinSize()); + this->Layout(); + this->Centre( wxBOTH ); +} + +#endif//_SINGLE_PAN_DLG_H_ diff --git a/include/wx/ValidityPanel.h b/include/wx/ValidityPanel.h new file mode 100644 index 0000000..2ce1a06 --- /dev/null +++ b/include/wx/ValidityPanel.h @@ -0,0 +1,106 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _VALIDITY_EVENT_H_ +#define _VALIDITY_EVENT_H_ + +#include + +#include + +//@brief: an event to be emmited when the shape changes +class ValidityChangedEvent : public wxCommandEvent { + bool m_vld; + std::string m_msg; + public: + ValidityChangedEvent(wxEventType commandEventType = wxEVT_NULL, int id = 0) : wxCommandEvent(commandEventType, id), m_vld(false) {} + void SetValid(const bool vld) {m_vld = vld;} + bool GetValid() const {return m_vld;} + void SetMsg(const std::string msg) {m_msg = msg;} + std::string GetMsg() const {return m_msg;} + virtual wxEvent *Clone() const { return new ValidityChangedEvent(*this); } +}; +wxDEFINE_EVENT(VALID_CHANGED, ValidityChangedEvent); +typedef void (wxEvtHandler::*ValidityChangedEventFunction)(ValidityChangedEvent&); +#define ValidityChangedEventHandler(func) wxEVENT_HANDLER_CAST(ValidityChangedEventFunction, func) +#define EVT_VALID_CHANGED(id, func) wx__DECLARE_EVT1(VALID_CHANGED, id, ValidityChangedEventHandler(func)) + + +class ValidityPanel : public wxPanel { + protected: + bool m_first ;//has at least one test been processed + bool m_valid ;//is the panel currently valid + std::string m_lstMsg;//last message for validity + + //@brief: test for and essentially emit a validity changed event + virtual void testValid(); + + //@brief: force update the status message + void updateStatus(std::string str); + + public: + //@brief : sanity check the current state + //@return: true if the values parsed from the panel are reasonable, false otherwise + virtual bool isValid() const {return validMsg().empty();} + virtual std::string validMsg() const {return "";} + + ValidityPanel( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 400,400 ), long style = wxTAB_TRAVERSAL, const wxString& name = wxEmptyString ) : wxPanel( parent, id, pos, size, style, name ), m_first(true), m_valid(true) {} + +}; + +//@brief: emit a validity changed event +void ValidityPanel::testValid() { + const bool vld = isValid (); + const std::string msg = validMsg(); + if(vld != m_valid || msg != m_lstMsg || m_first) { + m_first = false; + m_valid = vld; + m_lstMsg = msg; + ValidityChangedEvent event(VALID_CHANGED, GetId()); + event.SetEventObject(this); + event.SetValid(vld); + event.SetMsg(msg); + ProcessWindowEvent(event);//send + } +} + +//@brief: force update the status message +void ValidityPanel::updateStatus(std::string str) {ValidityChangedEvent event(VALID_CHANGED, GetId()); + event.SetEventObject(this); + event.SetValid(isValid()); + event.SetMsg(str); + ProcessWindowEvent(event);//send +} + +#endif//_VALIDITY_EVENT_H_ diff --git a/include/wx/ValidityWizard.h b/include/wx/ValidityWizard.h new file mode 100644 index 0000000..31fc6a6 --- /dev/null +++ b/include/wx/ValidityWizard.h @@ -0,0 +1,236 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _VALID_WIZ_H_ +#define _VALID_WIZ_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wx/ValidityPanel.h" + +/////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +/// Class ValidityWizard +/////////////////////////////////////////////////////////////////////////////// +class ValidityWizard : public wxFrame +{ + private: + bool m_finished = false; + + protected: + wxSimplebook* m_book; + wxSimplebook* m_ctrlBook; + wxPanel* m_ctrlPanel; + wxButton* m_btnPrev; + wxButton* m_btnNext; + wxButton* m_btnCancel; + wxStatusBar* m_statusBar; + + // Virtual event handlers, overide them in your derived class + virtual void PageChanged( wxBookCtrlEvent& event ); + virtual void PageChanging( wxBookCtrlEvent& event ) {event.Skip();}// if(!((ValidityPanel*)m_book->GetPage(event.GetOldSelection()))->isValid()) event.Veto(); } + + virtual void OnBack( wxCommandEvent& event ); + virtual void OnNext( wxCommandEvent& event ); + + virtual void OnCancel( wxCommandEvent& event ) { Close(); } + virtual void ValidityChanged( ValidityChangedEvent& event ); + + void AddPage(ValidityPanel* panel); + + public: + + + ValidityWizard( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("My Wizard"), const wxPoint& pos = wxDefaultPosition); + + ~ValidityWizard(); + +}; + + +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version Aug 31 2019) +// http://www.wxformbuilder.org/ +// +// PLEASE DO *NOT* EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////// + +void ValidityWizard::PageChanged( wxBookCtrlEvent& event ) { + m_btnPrev->Enable(0 != event.GetSelection());//disable the previous button on first page + m_btnNext->SetLabel( 1 + event.GetSelection() == m_book->GetPageCount() ? "Finish" : "Next >");//update next button on last page + ValidityPanel* pan = (ValidityPanel*)m_book->GetPage(event.GetSelection()); + m_btnNext->Enable(pan->isValid());// + m_statusBar->SetStatusText(pan->validMsg()); +} + +void ValidityWizard::OnBack( wxCommandEvent& event ) { + m_book->SetSelection(m_book->GetSelection() - 1); +} + +void ValidityWizard::OnNext( wxCommandEvent& event ) { + int idx = m_book->GetSelection() + 1;//get index of next page + int num = m_book->GetPageCount(); + if(idx == num) {//handle the last page specially + m_finished = true; + Close();//finish button + } else { + if(idx + 1 == num) m_btnNext->SetLabel("Finish");//update label on last page + m_book->SetSelection(idx);//move to current page + + } +} + +void ValidityWizard::ValidityChanged( ValidityChangedEvent& event ) { + if(event.GetEventObject() == (wxObject*)m_book->GetPage(m_book->GetSelection())) {//make sure the current page emmited the event + m_btnNext->Enable(event.GetValid()); + m_statusBar->SetStatusText(event.GetMsg()); + } +} + +void ValidityWizard::AddPage(ValidityPanel* panel) { + if(0 == m_book->GetPageCount()) m_statusBar->SetStatusText(panel->validMsg()); + m_book->AddPage(panel, wxT(""), false ); + wxSize minSize = m_book->GetPage(0)->GetBestSize(); + for(size_t i = 1; i < m_book->GetPageCount(); i++) { + wxSize pgSize = m_book->GetPage(i)->GetBestSize(); + minSize.x = std::max(minSize.x, pgSize.x); + minSize.y = std::max(minSize.y, pgSize.y); + } + m_book->SetPageSize(minSize); + panel->Connect( VALID_CHANGED, ValidityChangedEventHandler( ValidityWizard::ValidityChanged ), NULL, this ); +} + +ValidityWizard::ValidityWizard( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos) : wxFrame( parent, id, title, pos, wxSize( 325,350 ), wxCAPTION|wxCLOSE_BOX|wxSYSTEM_MENU|wxTAB_TRAVERSAL ) +{ + this->SetSizeHints( wxDefaultSize, wxDefaultSize ); + + wxBoxSizer* bSizer; + bSizer = new wxBoxSizer( wxVERTICAL ); + + m_book = new wxSimplebook( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); + + bSizer->Add( m_book, 1, wxEXPAND, 5 ); + + m_ctrlBook = new wxSimplebook( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); + m_ctrlPanel = new wxPanel( m_ctrlBook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + wxBoxSizer* vSizer; + vSizer = new wxBoxSizer( wxVERTICAL ); + + wxStaticLine* staticLine = new wxStaticLine( m_ctrlPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + vSizer->Add( staticLine, 0, wxEXPAND|wxALL, 5 ); + + wxBoxSizer* hSizer; + hSizer = new wxBoxSizer( wxHORIZONTAL ); + + + hSizer->Add( 0, 0, 1, wxEXPAND, 5 ); + + m_btnPrev = new wxButton( m_ctrlPanel, wxID_ANY, wxT("< Back"), wxDefaultPosition, wxDefaultSize, 0 ); + hSizer->Add( m_btnPrev, 0, wxALL, 5 ); + + m_btnNext = new wxButton( m_ctrlPanel, wxID_ANY, wxT("Next >"), wxDefaultPosition, wxDefaultSize, 0 ); + hSizer->Add( m_btnNext, 0, wxALL, 5 ); + + m_btnCancel = new wxButton( m_ctrlPanel, wxID_ANY, wxT("Cancel"), wxDefaultPosition, wxDefaultSize, 0 ); + hSizer->Add( m_btnCancel, 0, wxALL, 5 ); + + + vSizer->Add( hSizer, 1, wxEXPAND, 5 ); + + + m_ctrlPanel->SetSizer( vSizer ); + m_ctrlPanel->Layout(); + vSizer->Fit( m_ctrlPanel ); + m_ctrlBook->AddPage( m_ctrlPanel, wxT("a page"), false ); + + bSizer->Add( m_ctrlBook, 0, wxEXPAND, 5 ); + + + this->SetSizer( bSizer ); + this->Layout(); + m_statusBar = this->CreateStatusBar( 1, wxSTB_ELLIPSIZE_END|wxSTB_SHOW_TIPS, wxID_ANY ); + + this->Centre( wxBOTH ); + + //disable both buttons and make next button defaultx + m_btnPrev->Disable(); + m_btnNext->Disable(); + m_btnNext->SetDefault(); + + // Connect Events + m_book->Connect( wxEVT_COMMAND_BOOKCTRL_PAGE_CHANGED, wxBookCtrlEventHandler( ValidityWizard::PageChanged ), NULL, this ); + m_book->Connect( wxEVT_COMMAND_BOOKCTRL_PAGE_CHANGING, wxBookCtrlEventHandler( ValidityWizard::PageChanging ), NULL, this ); + m_btnPrev->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ValidityWizard::OnBack ), NULL, this ); + m_btnNext->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ValidityWizard::OnNext ), NULL, this ); + m_btnCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ValidityWizard::OnCancel ), NULL, this ); +} + +ValidityWizard::~ValidityWizard() +{ + // Disconnect Events + m_book->Disconnect( wxEVT_COMMAND_BOOKCTRL_PAGE_CHANGED, wxBookCtrlEventHandler( ValidityWizard::PageChanged ), NULL, this ); + m_book->Disconnect( wxEVT_COMMAND_BOOKCTRL_PAGE_CHANGING, wxBookCtrlEventHandler( ValidityWizard::PageChanging ), NULL, this ); + m_btnPrev->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ValidityWizard::OnBack ), NULL, this ); + m_btnNext->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ValidityWizard::OnNext ), NULL, this ); + m_btnCancel->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ValidityWizard::OnCancel ), NULL, this ); + + + for(size_t i = 0; i < m_book->GetPageCount(); i++) { + m_book->GetPage(i)->Disconnect( VALID_CHANGED, ValidityChangedEventHandler( ValidityWizard::ValidityChanged ), NULL, this ); + } + m_book->DeleteAllPages(); +} + +#endif//_VALID_WIZ_H_ diff --git a/include/xtal/constants.hpp b/include/xtal/constants.hpp new file mode 100644 index 0000000..26c4270 --- /dev/null +++ b/include/xtal/constants.hpp @@ -0,0 +1,109 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019, William C. Lenthe * + * All rights reserved. * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _XTAL_CONSTANTS_ +#define _XTAL_CONSTANTS_ + +#include "../constants.hpp" + +#include + +#define XTAL_USE_H5 + +namespace xtal { + const int& pijk = emsphinx::pijk; + + //@brief: helper class to hold commonly used floating point constants + template + struct Constants { + //make sure that the Real type has the appropriate attributes + static_assert(std::is_floating_point::value, "Real must be a floating point type");//disallow integers + static_assert(std::numeric_limits::has_infinity, "Real must have infinity");//must have ieee infinity (if this is relaxed inf should be set to max fp) + + //simple mathematical constants + static const Real pi ;//pi + static const Real pi2 ;//pi*2 + static const Real pi_2 ;//pi/2 + static const Real r2 ;//sqrt(2) + static const Real r3 ;//sqrt(3) + static const Real r1_2 ;//sqrt(1/2) + static const Real r1_3 ;//sqrt(1/3) + static const Real r3_4 ;//sqrt(3/4) == sqrt(3) / 2 + + //conversion constants + static const Real dg2rd;//degrees to radians (pi / 180) + static const Real rd2dg;//radians to degrees (180 / pi) + + //numerical precision constants + static const Real eps ;//machine epsilon + static const Real rEps ;//sqrt(eps) + static const Real thr ;//threshold for 0 (machine epsilon * small factor) + static const Real inf ;//infinity + + //rotations constants + static const Real hoR ;//radius of homochoric sphere + static const Real hoR2 ;//radius of homochoric sphere^2 + static const Real cuA ;//side length of cubochoric cube + static const Real cuA_2;//side length of cubochoric cube / 2 + static const Real pjik ;//pijk as fp + static const Real tp_8 ;//tan(pi/8) == sqrt(2) - 1 + static const Real tp_12;//tan(pi/12) == 2 - sqrt(3) + }; + +} + +#include +#include + +namespace xtal { + //constants are given with 72 decimal places which is enough for IEEE octuple precision (if you ever need to support 512 bit fp numbers you'll need to recompute these...) + + //simple mathematical constants + template const Real Constants::pi = Real(3.14159265358979323846264338327950288419716939937510582097494459230781641 ); + template const Real Constants::pi2 = Real(6.28318530717958647692528676655900576839433879875021164194988918461563281 ); + template const Real Constants::pi_2 = Real(1.57079632679489661923132169163975144209858469968755291048747229615390820 ); + template const Real Constants::r2 = Real(1.41421356237309504880168872420969807856967187537694807317667973799073248 ); + template const Real Constants::r3 = Real(1.73205080756887729352744634150587236694280525381038062805580697945193302 ); + template const Real Constants::r1_2 = Real(0.707106781186547524400844362104849039284835937688474036588339868995366239); + template const Real Constants::r1_3 = Real(0.577350269189625764509148780501957455647601751270126876018602326483977672); + template const Real Constants::r3_4 = Real(0.866025403784438646763723170752936183471402626905190314027903489725966508); + + //conversion constants + template const Real Constants::dg2rd = Real(0.0174532925199432957692369076848861271344287188854172545609719144017100911); + template const Real Constants::rd2dg = Real(57.2957795130823208767981548141051703324054724665643215491602438612028471 ); + + //numerical precision constants + template const Real Constants::eps = std::numeric_limits::epsilon(); + template const Real Constants::rEps = std::sqrt(Constants::eps); + template const Real Constants::thr = Constants::eps * Real(10); + template const Real Constants::inf = std::numeric_limits::infinity(); + + //rotations constants + template const Real Constants::hoR = Real(1.33067003949146879092560751376547158439759677721615056766447114777522727);//(pi * 3 / 4) ^ (1/3) + template const Real Constants::hoR2 = Real(1.77068275400022711161806356533586473059468238635461597721935515693649391);//(pi * 3 / 4) ^ (2/3) + template const Real Constants::cuA = Real(2.14502939711102560007744410094123559748666736547155699029161726142592328);//pi ^ (2/3) + template const Real Constants::cuA_2 = Real(1.07251469855551280003872205047061779874333368273577849514580863071296164);//pi ^ (2/3) + template const Real Constants::pjik = Real(pijk); + template const Real Constants::tp_8 = Real(0.41421356237309504880168872420969807856967187537694807317667973799073248); + template const Real Constants::tp_12 = Real(0.26794919243112270647255365849412763305719474618961937194419302054806698); +} + +#endif// _XTAL_CONSTANTS_ diff --git a/include/xtal/diagram.hpp b/include/xtal/diagram.hpp new file mode 100644 index 0000000..a86cce5 --- /dev/null +++ b/include/xtal/diagram.hpp @@ -0,0 +1,620 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _xtal_diagram_h_ +#define _xtal_diagram_h_ + +#include "util/svg.hpp" +#include "sht/square_sht.hpp"//lambert::sphereToSquare +#include "idx/master.hpp"//MasterPattern +#include "util/image.hpp"//interpolation +#include "symmetry.hpp" + +namespace xtal { + struct Diagram { + //supported projection types (currently only circular projections) + enum class Type { + Stereo ,//stereographic (conformal) + Lambert,//lambert azimuthal (area preserving) + }; + + //@brief : construct a crystal diagram + //@param t : type of projection + //@param d : size of projection + Diagram(const Type t = Type::Stereo, const size_t d = 100); + + //@brief : construct a crystal diagram for a specific point group + //@param pg: point group to construct diagram for + //@param t : type of projection + Diagram(PointGroup pg, const Type t = Type::Stereo) : Diagram(t) {add(pg);} + + //@brief : construct a crystal diagram for a master pattern + //@param mp: master pattern to construct diagram for + //@param c : color for symmetry elements + //@param t : type of projection + template Diagram(emsphinx::MasterPattern& mp, svg::Color c = svg::Color(0.375, 0, 0), const Type t = Type::Stereo); + + //@brief : construct a crystal diagram for an arbitrary color function + //@param pg: point group to construct diagram for + //@param cf: coloring function that converts from a unit crystallographic direction to an rgb color (rgb in [0,1]) + //@param c : color for symmetry elements + //@param t : type of projection + template Diagram(PointGroup pg, std::function cf, svg::Color c = svg::Color(0, 0, 0), const Type t = Type::Stereo); + + //@brief : construct an IPF colored crystal diagram + //@param pg: point group to construct diagram for + //@param t : type of projection + static Diagram IpfLegend(PointGroup pg, const Type t = Type::Stereo); + + //@brief : set the color to use for drawing elements + //@param r: red [0,1] + //@param g: green [0,1] + //@param b: blue [0,1] + void setColor(double r, double g, double b) {clr.setRGB(r, g, b);} + + //@brief : add a rotational symmetry operator + //@param x : x coordinate of rotation axis + //@param y : y coordinate of rotation axis + //@param z : z coordinate of rotation axis + //@param fld: order of rotational symmetry axis, negative for inversion symmetry + //@param scl: scale factor for symbol size ([0.5, 1.5] is reasonable) + //@note : currently only 2, 3, 4, 6, -1, -2, -3, -4, and -6 are supported + void addRotor(double x, double y, double z, int fld, double scl = 1); + + //@brief : add a mirror plane + //@param x: x coordinate of rotation axis + //@param y: y coordinate of rotation axis + //@param z: z coordinate of rotation axis + void addMirror(double x, double y, double z); + + //@brief: add an inversion symmetry marker + //@param scl: scale factor for symbol size ([0.5, 1.5] is reasonable) + void addInversion(double scl = 1); + + //@brief : add the symmetry operators associated with a point group + //@param scl: scale factor for symbol size ([0.5, 1.5] is reasonable) + //@param pg : point group to add symmetry operators of + void add(PointGroup pg, const double scl = 1); + + //@brief : add a dot to the projections + //@paran n : direction to add spot for (+/-), will be normalized + //@param c : color to use + //@param scl: scale factor for symbol size ([0.5, 1.5] is reasonable) + void addDot(double const * const n, svg::Color c, const double scl = 1); + + //@brief : add a label to the projections + //@param lbl: label to add + //@param scl: scale factor for symbol size ([0.5, 1.5] is reasonable) + void addLabel(std::string lbl, const double scl = 1); + + //@brief : get the current svg for a hemisphere + //@param nth: true/false for north/south hemisphere + //@return : svg for selected hemisphere + svg::SVG getHemi(const bool nth = true) const {return nth ? nh : sh;} + + private: + const size_t dim ;//canvas size (could be a floating point type if needed...) + Type type;//diagram projection type + svg::SVG nh ;//diagram of north hemisphere as svg + svg::SVG sh ;//diagram of north hemisphere as svg + svg::Color clr ;//color to use for markers / lines + + //@brief : project a unit direction to the canvas + //@param x : x coordinate of rotation axis + //@param y : y coordinate of rotation axis + //@param z : z coordinate of rotation axis + //@param X : X projected coordinate in canvas + //@param Y : Y projected coordinate in canvas + void project(double x, double y, double z, double& X, double& Y) const; + + //@brief : unproject a vector in the unit circle to the sphere + //@param X : X projected coordinate + //@param Y : Y projected coordinate + //@param x : x coordinate of unit direction on sphere + //@param y : y coordinate of unit direction on sphere + //@param z : z coordinate of unit direction on sphere + //@return : true/false if XY was a valid direction (in the north hemisphere) + bool unproject(double X, double Y, double& x, double& y, double& z) const; + + //@brief: convert a Catmull-Rom spline to a cubic Bezier curve + //@param p: Catmull-Rom control points (4) + //@param t: Catmull-Rom knots (4) + //@param c: location to write cubic spline control points (4) + //@note : c[0] = p[1], c[3] = p[2] + static void CatmullRom2Bezier(double const * const p, double const * const t, double * const c); + }; +} + +#include + +#include "constants.hpp" + +namespace xtal { + //@brief : construct a crystal diagram + //@param t : type of projection + //@param d : size of projection + Diagram::Diagram(const Type t, const size_t d) : dim(d), type(t), nh((double)dim, (double)dim), sh((double)dim, (double)dim), clr(0, 0, 0) { + const double dDim = (double)dim; + svg::Circle eq(dDim/2, dDim/2, dDim/2); + eq.setStroke(svg::Stroke(dDim / 500)); + nh.add(eq); + sh.add(eq); + } + + //@brief : construct a crystal diagram for a master pattern + //@param mp: master pattern to construct diagram for + //@param c : color for symmetry elements + //@param t : type of projection + template + Diagram::Diagram(emsphinx::MasterPattern& mp, svg::Color c, const Type t) : Diagram(t, mp.dim) { + //create image for north/south hemispheres + svg::Image imNh(dim, dim, svg::Image::GrayAlpha); + svg::Image imSh(dim, dim, svg::Image::GrayAlpha); + + //determine pixel range so we can rescale to [0,255] + auto minMaxNh = std::minmax_element(mp.nh.begin(), mp.nh.end()); + auto minMaxSh = std::minmax_element(mp.sh.begin(), mp.sh.end()); + const Real vMin = std::min(*minMaxNh.first , *minMaxSh.first ); + const Real vMax = std::max(*minMaxNh.second, *minMaxSh.second); + const Real vDel = vMax - vMin; + std::function to8Bit = [&](const Real& v){return (uint8_t)std::round((v - vMin) * Real(255) / vDel);}; + + //interpolate from square lambert to stereographic + double n[3], ix, iy; + image::BiPix pix; + for(size_t j = 0; j < dim; j++) { + const double Y = -((Real(j) / (dim - 1)) * 2 - 1);//[-1,1], negate for image convention + for(size_t i = 0; i < dim; i++) { + const double X = (Real(i) / (dim - 1)) * 2 - 1;//[-1,1] + if(unproject(X, Y, n[0], n[1], n[2])) {//unstereographic project + emsphinx::square::lambert::sphereToSquare(n[0], n[1], n[2], ix, iy);//square lambert project + pix.bilinearCoeff(ix, iy, dim, dim);//compute bilinear interpolation in square lambert image + const size_t idxIm = j * dim + i;//get index of output image + imNh.buff[2*idxIm + 0] = to8Bit(pix.interpolate(mp.nh.data())); + imNh.buff[2*idxIm + 1] = 0xFF;//alpha + imSh.buff[2*idxIm + 0] = to8Bit(pix.interpolate(mp.sh.data())); + imSh.buff[2*idxIm + 1] = 0xFF;//alpha + } + } + } + + //add images + nh.add(imNh); + sh.add(imSh); + + //add symmetry + clr = c; + add(mp.pointGroup(), 0.67); + } + + //@brief : construct a crystal diagram for an arbitrary color function + //@param pg: point group to construct diagram for + //@param cf: coloring function that converts from a unit crystallographic direction to an rgb color (rgb in [0,1]) + //@param c : color for symmetry elements + //@param t : type of projection + template + Diagram::Diagram(PointGroup pg, std::function cf, svg::Color c, const Type t) : Diagram(t, 512) { + //create image for north/south hemispheres + svg::Image imNh(dim, dim, svg::Image::RGBA); + svg::Image imSh(dim, dim, svg::Image::RGBA); + //loop over pixels of images computing color + double n[3]; + for(size_t j = 0; j < dim; j++) { + const double Y = -((Real(j) / (dim - 1)) * 2 - 1);//[-1,1], negate for image convention + for(size_t i = 0; i < dim; i++) { + const double X = (Real(i) / (dim - 1)) * 2 - 1;//[-1,1] + if(unproject(X, Y, n[0], n[1], n[2])) {//unstereographic project + //compute colors + Real rgbNh[3], rgbSh[3]; + cf(n, rgbNh);//compute color for north hemisphere + n[2] = -n[2]; + cf(n, rgbSh);//compute color for south hemisphere + + //convert to 8 bit + for(size_t k = 0; k < 3; k++) { + rgbNh[k] = std::round(rgbNh[k] * 255); + rgbSh[k] = std::round(rgbSh[k] * 255); + } + + //save result + const size_t idxIm = (j * dim + i) * 4;//get index of output image + for(size_t k = 0; k < 3; k++) imNh.buff[idxIm + k] = (uint8_t) rgbNh[k]; + imNh.buff[idxIm + 3] = 0xFF;//alpha + for(size_t k = 0; k < 3; k++) imSh.buff[idxIm + k] = (uint8_t) rgbSh[k]; + imSh.buff[idxIm + 3] = 0xFF;//alpha + } + } + } + + //add images + nh.add(imNh); + sh.add(imSh); + + //add symmetry + clr = c; + add(pg, 0.67); + } + + //@brief : construct an IPF colored crystal diagram + //@param pg: point group to construct diagram for + //@param t : type of projection + Diagram Diagram::IpfLegend(PointGroup pg, const Type t) { + std::function cf = std::bind(&PointGroup::ipfColor, &pg, std::placeholders::_1, std::placeholders::_2, sph2rgb); + return Diagram(pg, cf, svg::Color(0, 0, 0), t); + } + + //@brief : add a rotational symmetry operator + //@param x : x coordinate of rotation axis + //@param y : y coordinate of rotation axis + //@param z : z coordinate of rotation axis + //@param fld: order of rotational symmetry axis, negative for inversion symmetry + //@param scl: scale factor for symbol size ([0.5, 1.5] is reasonable) + //@note : currently only 2, 3, 4, 6, -1, -2, -3, -4, and -6 are supported + void Diagram::addRotor(double x, double y, double z, int fld, double scl) { + //compute scaled constant + const int aFld = std::abs(fld); + const double rad = double(dim) * scl / 20; + + //project to hemisphere and compute rotation + double nX, nY, sX, sY; + if(std::signbit(z)) { + x = -x; + y = -y; + z = -z; + } + project( x, y, z, nX, nY); + project(-x, -y, -z, sX, sY); + const bool rot2Fold = false;//should lenses be perpendicular (true) or parallel (false) to the equator + double nTheta = 90 + std::atan2(nY - double(dim) / 2, nX - double(dim) / 2) * 180.0 / Constants::pi; + double sTheta = 90 + std::atan2(sY - double(dim) / 2, sX - double(dim) / 2) * 180.0 / Constants::pi; + if(std::max(std::fabs(x), std::fabs(y)) < 1e-6) {//don't rotate if we're extremely close to the pole + if((aFld == 2 || fld == -4) && !rot2Fold) { + nTheta = sTheta = 90;//except for lens which I'll always keep vertical at the poles + } else { + nTheta = sTheta = 0; + } + } + + //build base shapes + double rx = rad; + double ry = rad / 2.5; + if(rot2Fold) std::swap(rx, ry); + svg::Circle cirK = svg::Circle(rad / 3); //1 fold symbol + svg::Ellipse ell = svg::Ellipse(rx, ry); //2 fold symbol + svg::Polygon ply = svg::Polygon::Regular(aFld , rad); //n fold symbol + svg::Polygon ply2 = svg::Polygon::Regular(aFld / 2, rad);//n/2 fold symbol + svg::Circle cirW = svg::Circle(rad / 5); //inversion symbol + + //lens + polygon black fill only + cirK.setFill(clr).removeStroke(); + ell .setFill(clr).removeStroke(); + ply .setFill(clr).removeStroke(); + ply2.setFill(clr).removeStroke(); + + //make dot white fill (inversion marker) + cirW.setFill(1, 1, 1).removeStroke(); + + //assemble marker + svg::Group mrk; + if(fld < 0) { + //inversion symmetry + if(aFld < 3) { + if(2 == aFld) { + mrk.add(ell);//use lens for 2 fold + mrk.add(cirW);//add white circle to center + } else { + throw std::runtime_error("rotoinversion axis must be at least -2"); + } + } else if(0 == aFld % 2) { + //even means there is an aFld / 2 pure rotation, use special symbol + ply.setFill(1, 1, 1); + ply.setStrokeColor(clr); + mrk.add(ply ); + 4 == aFld ? mrk.add(ell) : mrk.add(ply2); + } else { + //odd means both, just add dot + mrk.add(ply );//use lens for 2 fold + mrk.add(cirW);//add white circle to center + } + } else { + //no inversion symmetry + if(fld < 3) { + if(2 == fld) { + mrk.add(ell);//use lens for 2 fold + } else { + throw std::runtime_error("cannot add 0 or 1 fold rotational symmetry"); + } + } else { + //use polygon for 3+ fold + mrk.add(ply); + } + } + + //place marker in correct location handling equator + if(z == 0) { + nh.add(svg::Group(mrk).translate(nX, nY).rotate(nTheta)); + nh.add(svg::Group(mrk).translate(sX, sY).rotate(sTheta)); + sh.add(svg::Group(mrk).translate(nX, nY).rotate(nTheta)); + sh.add(svg::Group(mrk).translate(sX, sY).rotate(sTheta)); + } else { + nh.add(svg::Group(mrk).translate(nX, nY).rotate(nTheta)); + sh.add(svg::Group(mrk).translate(sX, sY).rotate(sTheta)); + } + } + + //@brief : add a mirror plane + //@param x: x coordinate of rotation axis + //@param y: y coordinate of rotation axis + //@param z: z coordinate of rotation axis + void Diagram::addMirror(double x, double y, double z) { + //normalize + const double mag = std::sqrt(x * x + y * y + z * z); + x /= mag; + y /= mag; + z /= mag; + + //check type of arc + const double dDim = (double)dim; + const double aZ = std::fabs(z); + if(std::fabs(aZ - 1.0) < 1e-6) {//plane normal is z axis + //add line at equator + svg::Circle eq(dDim / 2, dDim / 2, dDim / 2); + eq.setStroke(dDim / 100, clr); + nh.add(eq); + sh.add(eq); + } else if(aZ < 1e-6) {//plane normal lies in equator + double X1, Y1, X2, Y2; + project(-y, x, 0, X1, Y1); + project( y, -x, 0, X2, Y2); + svg::Line line(X1, Y1, X2, Y2); + line.setStroke(dDim / 100, clr); + nh.add(line); + sh.add(line); + } else {//general arc (circle in stereographic projection) + //compute intersection with equator + const double h = std::hypot(x, y); + double n[2] = {y/h, -x/h};//intersection with equator at +/- n + + //calculate some points on the great circle + std::vector ctrl;//x nh, y nh, x sh, y sh + const size_t extPts = 5;//how many extra control points should we insert + const size_t numPts = extPts + 2;//internal control points + end points + const size_t intPts = numPts - 1;//additional non path points (cubic control points shared by neighbors) + const size_t totPts = numPts + intPts; + for(size_t i = 0; i < totPts; i++) { + //build rotation matrix + const double theta = double(i) * Constants::pi / (totPts - 1);//fractional angle around half of great circle + const double c = std::cos(theta); + const double cc = 1.0 - c; + const double s = std::sin(theta); + double mat[3][2] = {//we don't need the last column (z of the vector being rotated is 0) + c + x * x * cc , x * y * cc + z * s, + y * x * cc - z * s, c + y * y * cc , + z * x * cc + y * s, z * y * cc - x * s, + }; + + //compute rotated vector + double rn[3] = { + mat[0][0] * n[0] + mat[0][1] * n[1], + mat[1][0] * n[0] + mat[1][1] * n[1], + mat[2][0] * n[0] + mat[2][1] * n[1], + }; + + //project + double X1, Y1, X2, Y2; + project( rn[0], rn[1], rn[2], X1, Y1); + project(-rn[0], -rn[1], -rn[2], X2, Y2); + + //add to control points + ctrl.push_back(X1); + ctrl.push_back(Y1); + ctrl.push_back(X2); + ctrl.push_back(Y2); + } + + //build arcs + svg::Path nhArc(ctrl[0], ctrl[1]), shArc(ctrl[2], ctrl[3]); + double t[4] = {0,1,2,3}; + double p[4][4] = { + ctrl[0], ctrl[0], ctrl[0], ctrl[4], + ctrl[1], ctrl[1], ctrl[1], ctrl[5], + ctrl[2], ctrl[2], ctrl[2], ctrl[6], + ctrl[3], ctrl[3], ctrl[3], ctrl[7], + }; + double c[4][4]; + for(size_t i = 1; i < totPts; i++) { + for(size_t j = 0; j < 4; j++) {//loop over the 4 points we need to convert splines for (X1, Y1, X2, Y2) + std::rotate(p[j], p[j] + 1, p[j] + 4);//shift all points back one + p[j][3] = ctrl[4 * (i + 1 == totPts ? i : i+1) + j];//grab new point + CatmullRom2Bezier(p[j], t, c[j]);//compute cubic bezier curve that goes through our control points + } + nhArc.curveTo(c[0][1], c[1][1], c[0][2], c[1][2], c[0][3], c[1][3], true);//accumulate arc + shArc.curveTo(c[2][1], c[3][1], c[2][2], c[3][2], c[2][3], c[3][3], true);//accumulate arc + } + nhArc.setStroke(dDim / 100, clr); + shArc.setStroke(dDim / 100, clr); + nh.add(nhArc); + sh.add(shArc); + } + } + + //@brief: add an inversion symmetry marker + //@param scl: scale factor for symbol size ([0.5, 1.5] is reasonable) + void Diagram::addInversion(double scl) { + //project to hemisphere + double nX, nY, sX, sY; + project(0, 0, 1, nX, nY); + project(0, 0,-1, sX, sY); + + //build base shapes + const double rad = double(dim) * scl / 20; + svg::Circle cirK = svg::Circle(rad / 3);//outer circle + svg::Circle cirW = svg::Circle(rad / 5);//inner circle + cirK.setFill(clr).removeStroke();//solid dot + cirW.setFill(1, 1, 1).removeStroke();//white dot + + //assemble marker and place + svg::Group mrk; + mrk.add(cirK);//use lens for 2 fold + mrk.add(cirW);//add white circle to center + nh.add(svg::Group(mrk).translate(nX, nY)); + sh.add(svg::Group(mrk).translate(sX, sY)); + } + + //@brief : add the symmetry operators associated with a point group + //@param pg : point group to add symmetry operators of + //@param scl: scale factor for symbol size ([0.5, 1.5] is reasonable) + void Diagram::add(PointGroup pg, const double scl) { + //add mirror planes + const uint_fast8_t numMir = pg.numMirror(); + double const * mir = pg.mirrors(); + for(uint_fast8_t i = 0; i < numMir; i++) addMirror(mir[3*i+0], mir[3*i+1], mir[3*i+2]); + + //add rotors + const uint_fast8_t numAx = pg.numRotAxis(); + double const * ax = pg.rotAxis(); + for(uint_fast8_t i = 0; i < numAx; i++) addRotor(ax[4*i+1], ax[4*i+2], ax[4*i+3], (int)ax[4*i+0], scl); + + //add inversion center + if(pg.inversion() && 3 != pg.zRot()) addInversion();//add an inversion dot if there isn't already one from the -3 axis + } + + //@brief : add a dot to the projections + //@paran n : direction to add spot for (+/-), will be normalized + //@param c : color to use + //@param scl: scale factor for symbol size ([0.5, 1.5] is reasonable) + void Diagram::addDot(double const * const n, svg::Color c, const double scl) { + //project to hemisphere + double nX, nY, sX, sY; + double x = n[0], y = n[1], z = n[2]; + if(std::signbit(z)) { + x = -x; + y = -y; + z = -z; + } + project( x, y, z, nX, nY); + project(-x, -y, -z, sX, sY); + + //build dot + svg::Circle dot = svg::Circle(double(dim) * scl / 60); + dot.setFill(c).removeStroke(); + + //place dot in correct location handling equator + if(z == 0) { + nh.add(dot.translate(nX, nY)); + nh.add(dot.translate(sX, sY)); + sh.add(dot.translate(nX, nY)); + sh.add(dot.translate(sX, sY)); + } else { + nh.add(dot.translate(nX, nY)); + sh.add(dot.translate(sX, sY)); + } + } + + //@brief : add a label to the projections + //@param lbl: label to add + //@param scl: scale factor for symbol size ([0.5, 1.5] is reasonable) + void Diagram::addLabel(std::string lbl, const double scl) { + svg::Text tx; + tx.text = lbl; + tx.size = scl * double(dim) / 12; + nh.add(tx); + sh.add(tx); + } + + //@brief : project a unit direction to the canvas + //@param x : x coordinate of rotation axis + //@param y : y coordinate of rotation axis + //@param z : z coordinate of rotation axis + //@param X : X projected coordinate in canvas + //@param Y : Y projected coordinate in canvas + void Diagram::project(double x, double y, double z, double& X, double& Y) const { + //normalize + const double mag = std::sqrt(x * x + y * y + z * z); + x /= mag; + y /= mag; + z /= mag; + + //stereographic project to [-1,1] + X = x / (std::fabs(z) + 1); + Y = y / (std::fabs(z) + 1); + + //rescale to canvas dimensinos + X = (1 + X) * double(dim) / 2; + Y = (1 - Y) * double(dim) / 2;//correct for image coordinate system + } + + //@brief : unproject a vector in the unit circle to the sphere + //@param X : X projected coordinate + //@param Y : Y projected coordinate + //@param x : x coordinate of unit direction on sphere + //@param y : y coordinate of unit direction on sphere + //@param z : z coordinate of unit direction on sphere + //@return : true/false if XY was a valid direction (in the north hemisphere) + bool Diagram::unproject(double X, double Y, double& x, double& y, double& z) const { + const double h2 = X * X + Y * Y; + if(h2 > 1.0) return false; + const double den = h2 + 1.0; + x = X * 2 / den; + y = Y * 2 / den; + z = (1.0 - h2) / den; + return true; + } + + //@brief: convert a Catmull-Rom spline to a cubic Bezier curve + //@param p: Catmull-Rom control points (4) + //@param t: Catmull-Rom knots (4) + //@param c: location to write cubic spline control points (4) + //@note : c[0] = p[1], c[3] = p[2] + void Diagram::CatmullRom2Bezier(double const * const p, double const * const t, double * const c) { + //https://stackoverflow.com/questions/30748316/catmull-rom-interpolation-on-svg-paths + const double t21 = t[2] - t[1]; + const double t20 = t[2] - t[0]; + const double t31 = t[3] - t[1]; + const double t10 = t[1] - t[0]; + const double t32 = t[3] - t[2]; + const double c1 = (t21 * t21) / (t20 * t10); + const double c2 = t10 / t20 ; + const double d1 = t32 / t31 ; + const double d2 = (t21 * t21) / (t31 * t32); + const double m1 = c1 * (p[1]-p[0]) + c2 * (p[2]-p[1]); + const double m2 = d1 * (p[2]-p[1]) + d2 * (p[3]-p[2]); + c[0] = p[1] ; + c[1] = p[1] + m1 / 3; + c[2] = p[2] - m2 / 3; + c[3] = p[2] ; + } +} + +#endif//_xtal_diagram_h_ diff --git a/include/xtal/hm.hpp b/include/xtal/hm.hpp new file mode 100644 index 0000000..4764711 --- /dev/null +++ b/include/xtal/hm.hpp @@ -0,0 +1,1434 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _HM_H_ +#define _HM_H_ + +#include "position.hpp" + +namespace xtal { + //Hermann-Maguin notation + //this class can be used to build short or full Hermann-Maguin name and generators for: + // * origin choice 1 (or 2 for multple setting groups) + // * arbitrary origin choice (user supplied) + // * rhombohedral or hexagonal (obverse) setting for R centered groups (reverse is trivially to implement if needed) + // * currently only monoclinic unique axis b cell choice 1 abc (should be able to do all 18) + // * currently only orhthorhombic abc (should be able to do all 6) + // * currently only P/I centered tetragonal (MAY be able to do multiple cell C/F) + // * currently only P centered trigonal/hexagonal (MAY be able to do multiple cell H) + class HermannMaguin { + public: + //@brief : construct a Hermman-Maguin notation from a space group number + //@param sg: space group number + HermannMaguin(const size_t sg = 1) {fromNumber(sg);} + + //@brief : construct a Hermman-Maguin notation from a space group number + //@param sg : space group number + //@param alt: should the alternate international (cell choice 2) be used instead of the default (cell choice 1) + //@note : throws if alt == true but there isn't a second cell choice, i.e. not one of - + // {48,50,59,68,70,85,86,88,125,126,129,130,133,134,137,138,141,142,201,203,222,224,227,228} + void fromNumber(const size_t sg, const bool alt = false); + + //@brief : construct a Hermman-Maguin notation from a string + //@param str: Hermman-Maguin representation + void fromString(char const * str); + + //@brief : convert the stored symbol to a string + //@return: string representation of Hermann-Maguin notation + std::string to_string() const; + + //@brief : convert to short symbol + //@param str: Hermman-Maguin representation (e.g. P m m m instead of P 2/m 2/m 2/m) + HermannMaguin shortSym() const; + + //@brief : change axis to new setting (orthorhombic groups only) + //@param cell: cell choice (must be one of 1, 2, or 3) + //@param axis: new axis, must be one of: {"b", "-b", "c", "-c", "a", "-a"} + //@return : updated symbol with new cell + //@note : throws for non-monoclinic groups + HermannMaguin changeMonoCell(const size_t cell, const std::string axis = "b") const; + + //@brief : change axis to new setting (orthorhombic groups only) + //@param axis: new axis, must be one of: {"abc", "bac", "cab", "cba", "bca", "acb"} + //@return : cell updated to new axis, e.g. Imma("cab") => Ibmm + //@note : throws for non-orthorhombic groups + HermannMaguin changeOrthoAxis(const std::string axis = "abc") const; + + //@brief : compare 2 symbols to check if they are the same + //@param rhs: other symbol to compare against + //@return : this == rhs + bool operator==(const HermannMaguin& rhs) const; + + //@brief : compare 2 symbols to check if they are the same + //@param rhs: other symbol to compare against + //@return : this != rhs + bool operator!=(const HermannMaguin& rhs) const {return !operator==(rhs);} + + //@brief : build generators from the Hermann-Maguin name + //@param xyz: shift from "origin of the symbol" to apply in 24th, e.g. 3 for 1/8 (NULL to use stored origin) + //@param hex: should the hexagonal setting be used for rhombohedral groups + std::vector generators(int8_t const * const xyz = NULL, const bool rHex = true) const; + + //@brief: remove the origin shift from this symbol + void clearOrigin() {ori[0] = ori[1] = ori[2] = 0;} + + private: + + //enumeration of symmetry types + enum Centering : int_fast8_t {P = 0x0, C = 0xC, A = 0xA, B = 0xB, I = 0x1, F = 0xF, R = 0x3, H = 0x6};//lattice centering types + enum Glide : int_fast8_t {m = 0x5, g = 0x7, g1 = 0x8, g2 = 0x9, a = 0xa, b = 0xb, c = 0xc, d = 0xd, e = 0xe, n = 0xf};//glide/mirror types + enum Family : int_fast8_t {//crystal families + Triclinic , + MonoclinicB , MonoclinicBarB, MonoclinicC , MonoclinicBarC, MonoclinicA , MonoclinicBarA, //ab̲c, cb̲̅a, abc̲, bac̲̅, a̲bc, a̲̅cb + OrthohombicABC, OrthohombicBAC, OrthohombicCAB, OrthohombicCBA, OrthohombicBCA, OrthohombicACB, //abc, bac̅, cab, c̅ba, bca, ac̅b (could add 2.22) + Tetragonal , Trigonal , Hexagonal , Cubic + }; + + //symmetry about a given axis + struct AxisSym { + int_fast8_t r;//rotational order (+/-)1, 2, 3, 4, or 6 + int_fast8_t s;//screw subscript (0, 1, 2, 3, 4, or 5) + int_fast8_t g;//glide plane type + bool operator==(const AxisSym& rhs) const {return r == rhs.r && s == rhs.s && g == rhs.g;} + }; + + //@brief : get the number of non-empty symmetry elements in this symbol + //@return: number of elements - 1, 2, or 3 e.g. 1 for "P 1" and 3 for "F m 3 m" + size_t numSym() const; + + //@brief: sanity check a symbol, determine the crystal family, and unabbreviate if needed + void validate(); + + //@brief : change axis to new setting (monoclinic groups only) + //@param axis: new axis, it and fam must be one of: + // Family::MonoclinicB ,//ab̲c + // Family::MonoclinicBarB,// cb̲̅a + // Family::MonoclinicC ,//abc̲ + // Family::MonoclinicBarC,// bac̲̅ + // Family::MonoclinicA ,//a̲bc + // Family::MonoclinicBarA,// a̲̅cb + //@param cell: cell choice (must be one of 1, 2, or 3) + //@return : updated symbol with new cell + HermannMaguin changeMonoCell(const size_t cell, const Family axis) const; + + //@brief : change axis to new setting (orthorhombic groups only) + //@param axis: new axis, it and fam must be one of: + // Family::OrthohombicABC == abc + // Family::OrthohombicBAC == bac̅ + // Family::OrthohombicCAB == cab + // Family::OrthohombicCBA == c̅ba + // Family::OrthohombicBCA == bca + // Family::OrthohombicACB == ac̅b + //@return : updated symbol with new cell + HermannMaguin changeOrthoAxis(const Family axis) const; + + //an extended Hermann-Maguin space group symbol is a centering, up to 3 symmetry symbols, and an origin shift + Family fam ;//easiest not to compute this repeatedly + int_fast8_t cen ;//lattice centering + AxisSym sym[3];//symmetry for primary, secondary, and tertiary axis + int_fast8_t ori[3];//shift from origin of the symbol + }; +} + +#include +#include +#include + +namespace xtal { + + //@brief : construct a Hermman-Maguin notation from a space group number + //@param sg : space group number + //@param alt: should the alternate international (cell choice 2) be used instead of the default (cell choice 1) + //@note : throws if alt == true but there isn't a second cell choice, i.e. not one of - + // {48,50,59,68,70,85,86,88,125,126,129,130,133,134,137,138,141,142,201,203,222,224,227,228} + void HermannMaguin::fromNumber(const size_t sg, const bool alt) { + // + // this table encodes the full Hermann-Maguin name of each space group along with 2 translations: + // -the translation from the 'origin of the symbol' to the international tables origin (choice 1) + // -the translation from international tables origin choice 1 ==> origin choice 2 (or 0, 0, 0 if there is only 1 choice) + // the full symbol for the standard setting is encoded (i.e. axis abc for monoclinic/orthrhombic and unique axis b, cell choice 1 for monoclinic) + // + // the symbols are encoded 'nibble wise' (i.e. a single hex value in [0x0, 0xf]) + // it is designed to be trivial for human interpretation with a few exceptions (parenthesis list logic for non less intuitive selections) + // the following conventions are used to encode different type of symmetry elements + // + // lattice centering - possibilities are enumerated in table 1.2.1 of the international tables + // P = 0x0 (no centering => 0) + // C = 0xC + // A = 0xA + // B = 0xB + // I = 0x1 (1 looks similar to I) + // F = 0xF + // R = 0x3 (rhombohedral groups are 3 fold symmetric) + // H = 0x6 (hexagonal is 6 fold symmetric) + // + // other symmetry elements - possibilities are enumerated in table 1.3.1 of the international tables + // + // glide types + // the logic for these is the most tenuous - to avoid confusion with rotation symbols 1, 2, 3, 4, and 6 are reserved + // after assigning like symbols (e.g. a -> 0xa) only 5 values remain: 5, 7, 8, 9, and f + // {g, g1, and g2} are assigned to {7, 8, and 9} so they form a block leaving 5/f for m/n + // n was somewhat arbitrarily assigned to f since it is conceptually similar to d and e glide + // m was somewhat arbitrarily assigned to 5 since m is very common and 5 isn't used anywhere else + // - = 0x0 (no glide) + // m = 0x5 + // g = 0x7 + // g1 = 0x8 + // g2 = 0x9 + // a = 0xa + // b = 0xb + // c = 0xc + // d = 0xd + // e = 0xe + // n = 0xf + // + // rotation types require 2 nibbles (1 byte) with the first and second nibble storing the rotational order and modifier flag respectively + // proper rotations + // 1 = 0x10 + // 2 = 0x20 + // 3 = 0x30 + // 4 = 0x40 + // 6 = 0x60 + // rotoinversions (0xb for \bar) + // -1 = 0x1b + // -2 = 0x2b + // -3 = 0x3b + // -4 = 0x4b + // -6 = 0x6b + // screw axis + // 2_1 = 0x21 + // 3_1, 3_2 = 0x31, 0x32 + // 4_1, 4_2, 4_3 = 0x41, 0x42, 0x43 + // 5_1, 5_2, 5_3, 5_4 = 0x51, 0x52, 0x53, 0x54 + // 6_1, 6_2, 6_3, 6_4, 6_5 = 0x61, 0x62, 0x63, 0x64, 0x65 + // + // components of origin translations are stored as 24ths + // 3/4 (18/24) doesn't fit in 1 nibble so it needs a special value + // 5/8, 7/8, 2/3, and 5/6 aren't needed for any space group origin shifts + // 0 = 0x0 + // 1/8 = 0x3 ( 3/24 ) + // 1/6 = 0x4 ( 4/24 ) + // 1/4 = 0x6 ( 6/24 ) + // 1/3 = 0x8 ( 8/24 ) + // 3/8 = 0x9 ( 9/24 ) + // 1/2 = 0xb ( 12/24 ) + // 3/4 = 0x7 ( minimal conflict with other symbols [7 is only used for 'g'] and 7 == 3+4 ) + // + // using the above conventions a complete Hermann-Maguin symbol can be compactly stored in a human readable 64 bit integer: + // nibble [ 1, 1]: centering + // nibble [ 2, 4]: primary axis symmetry (2 nibbles for rotation followed by 1 for glide) + // nibble [ 5, 7]: secondary axis symmetry + // nibble [ 8,10]: tertiary axis symmetry + // nibble [11,13]: {x,y,z} translation from "origin of the symbol" to international tables origin + // nibble [14,16]: {x,y,z} translation from origin choice 2 to choice 1 (000 for no alternate setting) + // + // for example consider space group 227 F 4/d -3 2/m (F d 3 m) [2 origin choices] + // the shift from 'origin of the symbol' to origin 1 is {0.375, 0.375, 0.375} --> {9,9,9} /24 + // the shift from origin 2 to origin 1 is {0.125, 0.125, 0.125} --> {3,3,3} /24 + // F --> 0xf + // 4/d --> 0x40d + // -3 --> 0x3b0 + // 2/m --> 0x205 + // 0x(f)(40d)(3b0)(205)(999)(333) ==> 0xf40d3b0205999333 + // + // similarly space for group 135 P 42/m 2/b 2/c (P 42/m b c) [1 origin choice] + // the shift from 'origin of the symbol' to origin 1 is {0.25, 0.75, 0} --> {6,18,0} /24 + // P --> 0x0 + // 42/m --> 0x425 + // 2/b --> 0x20b + // 2/c --> 0x20c + // 0x(0)(425)(20b)(20c)(670)(000) ==> 0x042520b20c670000 + // + static const uint64_t SGLut[230] = { + 0x0100000000000000, 0x01b0000000000000, 0x0100200100000000, 0x0100210100000000, 0xc100200100000000, // [ 1, 5] + 0x0100105100000000, 0x010010c100000000, 0xc100105100000000, 0xc10010c100000000, 0x0100205100000000, // [ 6, 10] + 0x0100215100060000, 0xc100205100000000, 0x010020c100006000, 0x010021c100066000, 0xc10020c100006000, // [ 11, 15] + 0x0200200200000000, 0x0200200210000000, 0x0210210200660000, 0x0210210210060000, 0xc200200210000000, // [ 16, 20] + 0xc200200200000000, 0xf200200200000000, 0x1200200200000000, 0x1210210210060000, 0x0105105200000000, // [ 21, 25] + 0x010510c210000000, 0x010c10c200000000, 0x010510a200600000, 0x010c10a210600000, 0x010f10c200060000, // [ 26, 30] + 0x010510f210000000, 0x010b10a200660000, 0x010f10a210660000, 0x010f10f200660000, 0xc105105200000000, // [ 31, 35] + 0xc10510c210000000, 0xc10c10c200000000, 0xa105105200000000, 0xa10e105200060000, 0xa10510a200600000, // [ 36, 40] + 0xa10e10a200660000, 0xf105105200000000, 0xf10d10d200990000, 0x1105105200000000, 0x110b10a200660000, // [ 41, 45] + 0x110510a200600000, 0x0205205205000000, 0x020f20f20f666777, 0x020c20c205000000, 0x020b20a20f660770, // [ 46, 50] + 0x021520520a600000, 0x020f21f20a060000, 0x020520f21a006000, 0x021c20c20a600000, 0x021b21a205660000, // [ 51, 55] + 0x021c21c20f660000, 0x020b21c215066000, 0x021f21f205660000, 0x021521520f000770, 0x021b20c21f606000, // [ 56, 60] + 0x021b21c21a666000, 0x021f21521a666000, 0xc20520c215006000, 0xc20520c21e066000, 0xc205205205000000, // [ 61, 65] + 0xc20c20c205000000, 0xc20520520e060000, 0xc20c20c20e666077, 0xf205205205000000, 0xf20d20d20d999333, // [ 66, 70] + 0x1205205205000000, 0x120b20a205660000, 0x121b21c21a666000, 0x121521521a066000, 0x0400000000000000, // [ 71, 75] + 0x0410000000000000, 0x0420000000000000, 0x0430000000000000, 0x1400000000000000, 0x1410000000670000, // [ 76, 80] + 0x04b0000000000000, 0x14b0000000000000, 0x0405000000000000, 0x0425000000000000, 0x040f0000000c0670, // [ 81, 85] + 0x042f0000000c6666, 0x1405000000000000, 0x141a000000673063, 0x0400200200000000, 0x0400210200670000, // [ 86, 90] + 0x0410200200006000, 0x0410210200673000, 0x0420200200000000, 0x0420210200676000, 0x0430200200006000, // [ 91, 95] + 0x0430210200679000, 0x1400200200000000, 0x1410200200679000, 0x0400105105000000, 0x040010b105670000, // [ 96, 100] + 0x042010c105000000, 0x042010f105660000, 0x040010c10c000000, 0x040010f10c670000, 0x042010510c000000, // [101, 105] + 0x042010b10c670000, 0x1400105105000000, 0x140010c1050c0000, 0x141010510d070000, 0x141010c10d060000, // [106, 110] + 0x04b0200105000000, 0x04b020010c006000, 0x04b0210105670000, 0x04b021010c676000, 0x04b0105200000000, // [111, 115] + 0x04b010c200006000, 0x04b010b200660000, 0x04b010f200666000, 0x14b0105200000000, 0x14b010c200006000, // [116, 120] + 0x14b0200105000000, 0x14b020010d079000, 0x0405205205000000, 0x040520c20c000000, 0x040f20b205670660, // [121, 125] + 0x040f20f20c676666, 0x040520b205670000, 0x040520f20c670000, 0x040f2052050c0670, 0x040f20c20c0c0670, // [126, 130] + 0x042520520c000000, 0x042520c205000000, 0x042f20b20c666676, 0x042f20f205666676, 0x042520b20c670000, // [131, 135] + 0x042520f205660000, 0x042f20520c0c6676, 0x042f20c2050c6676, 0x1405205205000000, 0x140520c2050c0000, // [136, 140] + 0x141a20520d073073, 0x141a20c20d063073, 0x0300000000000000, 0x0310000000000000, 0x0320000000000000, // [141, 145] + 0x3300000000000000, 0x03b0000000000000, 0x33b0000000000000, 0x0300100200000000, 0x0300200100000000, // [146, 150] + 0x0310100200004000, 0x0310200100004000, 0x0320100200008000, 0x0320200100008000, 0x3300200000000000, // [151, 155] + 0x0300105100000000, 0x0300100105000000, 0x030010c100000000, 0x030010010c000000, 0x3300105000000000, // [156, 160] + 0x330010c000000000, 0x03b0100205000000, 0x03b010020c000000, 0x03b0205100000000, 0x03b020c100000000, // [161, 165] + 0x33b0205000000000, 0x33b020c000000000, 0x0600000000000000, 0x0610000000000000, 0x0650000000000000, // [166, 170] + 0x0620000000000000, 0x0640000000000000, 0x0630000000000000, 0x06b0000000000000, 0x0605000000000000, // [171, 175] + 0x0635000000006000, 0x0600200200000000, 0x0610200200000000, 0x0650200200000000, 0x0620200200000000, // [176, 180] + 0x0640200200000000, 0x0630200200000000, 0x0600105105000000, 0x060010c10c000000, 0x063010c105000000, // [181, 185] + 0x063010510c000000, 0x06b0105200000000, 0x06b010c200000000, 0x06b0200105000000, 0x06b020010c000000, // [186, 190] + 0x0605105105000000, 0x060510c10c000000, 0x063510c105006000, 0x063510510c006000, 0x0200300000000000, // [191, 195] + 0xf200300000000000, 0x1200300000000000, 0x0210300000000000, 0x1210300000000000, 0x02053b0000000000, // [196, 200] + 0x020f3b0000666666, 0xf2053b0000000000, 0xf20d3b0000999333, 0x12053b0000000000, 0x021a3b0000666000, // [201, 205] + 0x121a3b0000666000, 0x0400300200000000, 0x0420300200000000, 0xf400300200000000, 0xf410300200666000, // [206, 210] + 0x1400300200000000, 0x0430300200000000, 0x0410300200000000, 0x1410300200000000, 0x04b0300105000000, // [211, 215] + 0xf4b0300105000000, 0x14b0300105000000, 0x04b030010f000000, 0xf4b030010c000000, 0x14b030010d000000, // [216, 220] + 0x04053b0205000000, 0x040f3b020f666666, 0x04053b020f000000, 0x040f3b0205666666, 0xf4053b0205000000, // [221, 225] + 0xf4053b020c000000, 0xf40d3b0205999333, 0xf40d3b020c333999, 0x14053b0205000000, 0x140a3b020d666000, // [226, 230] + }; + + //get the encoded name and break into 16 nibbles + if(sg < 1 || sg > 230) throw std::runtime_error("space group number must be in [1,230]");//sanity check value + uint64_t enc = SGLut[sg-1]; + int_fast8_t nibbles[16]; + for(size_t i = 0; i < 16; i++) { + nibbles[15-i] = (int_fast8_t)(enc & 0x000000000000000F);//extract nibble currently in last place + enc >>= 4;//shift encoded value over 1 nibble + } + + //correct rotoinversions being split across 2 nibbles + for(size_t i = 2; i < 10; i+= 3) {//loop over th 2nd nibble of the 3 symmetry elements + if(0xb == nibbles[i]) {//this is a rotoinversion indicator + nibbles[i-1] *= -1 ;//negate value from previous nibble (now that it is unpacked) + nibbles[i ] = 0x0;//there is no screw associated with rotoinversions + } + } + + //correct 18/24ths being stored as 0x7 in origins (last 6 nibbles) + for(size_t i = 10; i < 16; i++) {//loop over the translation elements + if(0x7 == nibbles[i]) {//this is 18/24ths stored as 7 + nibbles[i] = 0x12;//7 -> 18 + } + } + + //save family based on number and get encoded symbol from table + if (sg <= 2) fam = Family::Triclinic ; + else if(sg <= 15) fam = Family::MonoclinicB ; + else if(sg <= 74) fam = Family::OrthohombicABC; + else if(sg <= 142) fam = Family::Tetragonal ; + else if(sg <= 167) fam = Family::Trigonal ; + else if(sg <= 194) fam = Family::Hexagonal ; + else fam = Family::Cubic ; + + //next save elements + cen = nibbles[0];//extract centering from first nibble + for(size_t i = 0; i < 3; i++) {//loop over symmetry elements + sym[i].r = nibbles[3*i+1];//save rotation + sym[i].s = nibbles[3*i+2];//save screw + sym[i].g = nibbles[3*i+3];//save glide + } + + //finally save origin translation + std::copy(nibbles + 10, nibbles + 13, ori);//save origin choice 1 + if(alt) {//shift origin if needed + if(0 == nibbles[13] && 0 == nibbles[14] && 0 == nibbles[15]) + throw std::runtime_error("cannot select alternate origin for space groups with only 1 choice"); + for(size_t i = 0; i < 3; i++) { + ori[i] -= nibbles[13+i]; + if(ori[i] < 0) ori[i] += 24; + } + } + } + + //@brief : construct a Hermman-Maguin notation from a string + //@param str: Hermman-Maguin representation + void HermannMaguin::fromString(char const * str) { + // wrap input name as string stream to make processing easier + std::istringstream is(str); + + // extract first character and convert to upper case + char c; + if(!(is >> c)) throw std::runtime_error("failed to extract Bravais lattice centering from `" + is.str() + "'"); + c = std::toupper(c); + + //parse lattice centering from first character of symbol + switch(c) { + case 'P': cen = Centering::P; break; + case 'C': cen = Centering::C; break; + case 'A': cen = Centering::A; break; + case 'B': cen = Centering::B; break; + case 'I': cen = Centering::I; break; + case 'F': cen = Centering::F; break; + case 'R': cen = Centering::R; break; + case 'H': cen = Centering::H; break; + default : throw std::runtime_error("unknown lattice centering `" + std::string(1, c) + "'"); + } + + //now extract at least 1 and up to 3 additional symbols + for(size_t i = 0; i < 3; i++) sym[i].r = sym[i].s = sym[i].g = 0;//clear all symmetry in case there aren't 3 symbols in the name + for(size_t i = 0; i < 3; i++) {//loop over potential symmetry elements + //get next non-whitespace character + if(!(is >> c)) break;//if there isn't any characters left to extract we're done + + //the first character in a symbol should be a rotation or a mirror/glide type, handle roto-inversion specially + if('-' == c) { + sym[i].r = -1;//save negative sign in rotation axis + if(!std::isdigit(is.peek())) std::runtime_error("`" + is.str() + "' has `-' that isn't followed by a number"); + is.get(c);//replace c with the next digit + } else { + sym[i].r = 1; + } + + //now c should be either a number or a mirror/glide type + if(!std::isalnum(c)) throw std::runtime_error("`" + is.str() + " contains an unexpected character"); + + //handle numeric component (always first in compound symbols) + if(std::isdigit(c)) { + //extract number + switch(c) { + case '1'://intentional fall through + case '2'://intentional fall through + case '3'://intentional fall through + case '4'://intentional fall through + case '6': sym[i].r *= (c - '0'); break;//standard guarantees 0-9 are contigous + default : throw std::runtime_error("`" + is.str() + " has unexpected digit"); + } + + //now check for a screw axis + if(sym[i].r > 0) {//roto-inversion axis don't have screws + if('0' < is.peek() && is.peek() < c) {//the next character is a number in [1, r) + is.get(c);//replace c with the next digit + sym[i].s = c - '0';//get screw component + } + //else -> not a digit or a higher number (leave for next symbol) + } + + //lastly check for a compound numeric symbol e.g. 2/m + if('/' == is.peek()) { + is.get(c);//skip '/' + if(!std::isalpha(is.peek())) throw std::runtime_error("`" + is.str() + " has `/' followed by unexpected character");//or no character + is.get(c);//extract symbol after '/' + } else { + //if the next character after the numeric component is anything but '/' it is either the next symbol or a problem + } + } + + //there is a glide type symbol to parse (c is first character or first after '/') + if(std::isalpha(c)) { + c = std::tolower(c); + if(sym[i].r < 0) throw std::runtime_error("`" + is.str() + " has a compound rotoinversion symbol (unsupported)"); + switch(c) { + case 'm': sym[i].g = Glide::m ; break; + case 'g': { + switch(is.peek()) { + case '1': sym[i].g = Glide::g1; break; + case '2': sym[i].g = Glide::g2; break; + default : sym[i].g = Glide::g ; break; + } + } break; + case 'a': sym[i].g = Glide::a ; break; + case 'b': sym[i].g = Glide::b ; break; + case 'c': sym[i].g = Glide::c ; break; + case 'd': sym[i].g = Glide::d ; break; + case 'e': sym[i].g = Glide::e ; break; + case 'n': sym[i].g = Glide::n ; break; + default : throw std::runtime_error("`" + is.str() + " contains an unexpected character"); + } + } + //else -> the previous check for isalnum means we've already parsed a stand alone rotation + } + + //now we have the symbol, unabbreviate and sanity check + validate(); + } + + //@brief : convert the stored symbol to a string + //@return: string representation of Hermann-Maguin notation + std::string HermannMaguin::to_string() const { + const bool abbrev = false;//should abbreiated notation be used? (not implemented yet) + std::ostringstream os; + + //print lattice type + switch(cen) { + case Centering::P: os << 'P'; break; + case Centering::C: os << 'C'; break; + case Centering::A: os << 'A'; break; + case Centering::B: os << 'B'; break; + case Centering::I: os << 'I'; break; + case Centering::F: os << 'F'; break; + case Centering::R: os << 'R'; break; + case Centering::H: os << 'H'; break; + } + + //print symmetry elements + const size_t numEl = numSym(); + for(size_t i = 0; i < numEl; i++) { + os << ' '; + const bool hasRot = 1 != sym[i].r;//is there a rotation? + const bool hasGld = 0 != sym[i].g;//is there a glide? + + if(hasRot || hasGld) { + if(hasRot) { + os << (int)sym[i].r; + if(0 != sym[i].s) { + if(3 == sym[i].r && 3 == sym[i].s) { + os << "_{1,2}"; + } else { + os << (int)sym[i].s; + } + } + if(hasGld) os << '/'; + } + + if(hasGld) { + switch(sym[i].g) { + case Glide::m : os << "m" ; break; + case Glide::g : os << "g" ; break; + case Glide::g1: os << "g1"; break; + case Glide::g2: os << "g2"; break; + case Glide::a : os << "a" ; break; + case Glide::b : os << "b" ; break; + case Glide::c : os << "c" ; break; + case Glide::d : os << "d" ; break; + case Glide::e : os << "e" ; break; + case Glide::n : os << "n" ; break; + } + } + + } else { + os << "1"; + } + } + return os.str(); + } + + //@brief : convert to short symbol + //@param str: Hermman-Maguin representation + HermannMaguin HermannMaguin::shortSym() const { + //copy full symbol + HermannMaguin sSym(*this); + + //handle based on crystal system + switch(fam) { + case Family::Triclinic : + case Family::MonoclinicB : + case Family::MonoclinicBarB: + case Family::MonoclinicC : + case Family::MonoclinicBarC: + case Family::MonoclinicA : + case Family::MonoclinicBarA: break;//these families have no abbrerviation + + case Family::OrthohombicABC: + case Family::OrthohombicBAC: + case Family::OrthohombicCAB: + case Family::OrthohombicCBA: + case Family::OrthohombicBCA: + case Family::OrthohombicACB:// 2/m 2/m 2/m => mmm + if(0 != sym[0].g && 0 != sym[1].g && 0 != sym[2].g) { + sSym.sym[0].r = 1; sSym.sym[0].s = 0; + sSym.sym[1].r = 1; sSym.sym[1].s = 0; + sSym.sym[2].r = 1; sSym.sym[2].s = 0; + } + break; + + case Family::Tetragonal :// 4/m 2/m 2/m => 4/mmm + case Family::Hexagonal :// 6/m 2/m 2/m => 6/mmm + if(0 != sym[0].g && 0 != sym[1].g && 0 != sym[2].g) { + sSym.sym[1].r = 1; sSym.sym[1].s = 0; + sSym.sym[2].r = 1; sSym.sym[2].s = 0; + } + break; + + case Family::Trigonal : + if(-3 == sym[0].r) {// -3, -3 2/m 1, or -3 1 2/m + if(0 != sym[1].g) {sSym.sym[1].r = 1; sSym.sym[1].s = 0;}// -3 2/m 1 => -3 m 1 + if(0 != sym[2].g) {sSym.sym[2].r = 1; sSym.sym[2].s = 0;}// -3 1 2/m => -3 1 m + } + break; + + case Family::Cubic :// 4/m -3 2/m => m-3m + if(0 != sym[0].g){// m-3 or m-3m + sSym.sym[0].r = 1;// 4/m -3 => m -3 [and 4/m -3 2/m => m -3 2/m] + sSym.sym[0].s = 0;// 4/m -3 => m -3 [and 4/m -3 2/m => m -3 2/m] + if(0 != sym[2].g) {sSym.sym[2].r = 1; sSym.sym[2].s = 0;}// m -3 2/m => m -3 m + } + if(-3 == sym[1].r) sSym.sym[1].r = 3; + break; + } + return sSym; + } + + //@brief : change axis to new setting (orthorhombic groups only) + //@param cell: cell choice (must be one of 1, 2, or 3) + //@param axis: new axis, must be one of: {"b", "-b", "c", "-c", "a", "-a"} + //@return : updated symbol with new cell + //@note : throws for non-monoclinic groups + HermannMaguin HermannMaguin::changeMonoCell(const size_t cell, const std::string axis) const { + if (0 == axis.compare( "b")) return changeMonoCell(cell, Family::MonoclinicB ); + else if(0 == axis.compare("-b")) return changeMonoCell(cell, Family::MonoclinicBarB); + else if(0 == axis.compare( "c")) return changeMonoCell(cell, Family::MonoclinicC ); + else if(0 == axis.compare("-c")) return changeMonoCell(cell, Family::MonoclinicBarC); + else if(0 == axis.compare( "a")) return changeMonoCell(cell, Family::MonoclinicA ); + else if(0 == axis.compare("-a")) return changeMonoCell(cell, Family::MonoclinicBarA); + else throw std::runtime_error("new orhthorhombic cell must be one of {'b', '-b', 'c', '-c', 'a', '-a'}"); + } + + //@brief : change axis to new setting (orthorhombic groups only) + //@param axis: new axis, must be one of: {"abc", "bac", "cab", "cba", "bca", "acb"} + //@return : cell updated to new axis, e.g. Imma("cab") => Ibmm + //@note : throws for non-orthorhombic groups + HermannMaguin HermannMaguin::changeOrthoAxis(const std::string axis) const { + if (0 == axis.compare("abc")) return changeOrthoAxis(Family::OrthohombicABC); + else if(0 == axis.compare("bac")) return changeOrthoAxis(Family::OrthohombicBAC); + else if(0 == axis.compare("cab")) return changeOrthoAxis(Family::OrthohombicCAB); + else if(0 == axis.compare("cba")) return changeOrthoAxis(Family::OrthohombicCBA); + else if(0 == axis.compare("bca")) return changeOrthoAxis(Family::OrthohombicBCA); + else if(0 == axis.compare("acb")) return changeOrthoAxis(Family::OrthohombicACB); + else throw std::runtime_error("new orhthorhombic cell must be one of {'abc', 'bac', 'cab', 'cba', 'bca', 'acb'}"); + } + + //@brief : compare 2 symbols to check if they are the same + //@param rhs: other symbol to compare against + //@return : this == rhs + bool HermannMaguin::operator==(const HermannMaguin& rhs) const { + return cen == rhs.cen && fam == rhs.fam && + std::equal(sym, sym+3, rhs.sym) && + std::equal(ori, ori+3, rhs.ori); + } + + //@brief : build generators from the Hermann-Maguin name + //@param xyz: shift from "origin of the symbol" to apply in 24th, e.g. 3 for 1/8 (NULL to use stored origin) + //@param hex: should the hexagonal setting be used for rhombohedral groups + std::vector HermannMaguin::generators(int8_t const * const xyz, const bool rHex) const { + //get short symbol and number of symmetry elements once + const HermannMaguin sSym = shortSym(); + const size_t numEl = sSym.numSym(); + + //build up reference directions + int8_t refDirs[3][3] = {0}; + const bool isHexType = Family::Hexagonal == fam || Family::Trigonal == fam; + switch(fam) { + case Family::Triclinic : break; + + //there is still an issue with a few groups for extended orthorhombic, handle it explicitly here + case Family::OrthohombicBAC: + case Family::OrthohombicCAB: + case Family::OrthohombicCBA: + case Family::OrthohombicBCA: + case Family::OrthohombicACB: + //problem groups are those with only a single permutation + if(sym[0] == sym[1] && sym[1] == sym[2]) return changeOrthoAxis(Family::OrthohombicABC).generators(); + case Family::OrthohombicABC://no problem with default axis + + case Family::MonoclinicB : // these are all abc + case Family::MonoclinicC : // these are all abc + case Family::MonoclinicA : // these are all abc + case Family::MonoclinicBarB://these aren't actually all the same but it doesn't matter + case Family::MonoclinicBarC://the wrong directions should all be symmetry free + case Family::MonoclinicBarA: + refDirs[0][0] = 1;// 100 + refDirs[1][1] = 1;// 010 + refDirs[2][2] = 1;// 001 + break; + + case Family::Tetragonal : //intensional fall through + case Family::Hexagonal : refDirs[0][2] = 1;// 0 01 + refDirs[1][0] = 1;// 1 00 + refDirs[2][0] = 1; refDirs[2][1] = -1;// 1-10 + break; + + case Family::Trigonal : + //as hexagonal + refDirs[0][2] = 1; // 0 01 + refDirs[1][0] = 1; // 1 00 + refDirs[2][0] = 1; refDirs[2][1] = -1;// 1-10 + if(Centering::R == cen && !rHex) {//rhombohedral setting + refDirs[0][0] = refDirs[0][1] = 1;// 1 11 + refDirs[1][1] = -1;// 1-10 + } + break; + + case Family::Cubic : + refDirs[0][2] = 1 ;// 001 + refDirs[1][0] = refDirs[1][1] = refDirs[1][2] = 1;// 111 + refDirs[2][0] = refDirs[2][1] = 1;// 110, there are actually 2 different choices for the last entry + if(0 != sym[0].g) refDirs[2][1] = -1;// 1-10 for m3m + break; + } + + //next determine indicator direction + size_t indicator = 3;//many groups have no indicator + switch(fam) { + //no indicator + case Family::Triclinic : + case Family::MonoclinicB : + case Family::MonoclinicBarB: + case Family::MonoclinicC : + case Family::MonoclinicBarC: + case Family::MonoclinicA : + case Family::MonoclinicBarA: + case Family::Trigonal : break; + + //orthorhombic groups use c axis as indicator + case Family::OrthohombicABC:// 22(2) or mm(2) + case Family::OrthohombicBAC: if(0 == sym[2].g) indicator = 2; break; + case Family::OrthohombicCAB:// (2)22 or (2)mm + case Family::OrthohombicCBA: if(0 == sym[0].g) indicator = 0; break; + case Family::OrthohombicBCA:// 2(2)2 or m(2)m + case Family::OrthohombicACB: if(0 == sym[1].g) indicator = 1; break; + + case Family::Tetragonal :// (4)/mmm (4)22 (4)mm (-4)2m (-4)m2 [no indicator for 4/m 4 and -4] + case Family::Hexagonal :// (6)/mmm (6)22 (6)mm (-6)2m (-6)m2 [no indicator for 6/m 6 and -6] + if(3 == numEl) indicator = 0; + break; + + case Family::Cubic : + if(3 == numEl) {//432, -43m or m-3m + if(0 == sym[0].g) indicator = 0;//(4)32 (-4)3m + } else {//23 or m-3 + } + break; + } + + //start by accumulating centering translations + std::set gen; + GenPos p = GenPos::Identity(); + gen.insert(p);//all space groups have the identity operation [and lattice translations] + int8_t t[3] = {0, 0, 0}; + switch(cen) { + case Centering::P: break;//primitive + case Centering::C: t[0] = t[1] = 12; break;//C-face centered + case Centering::A: t[1] = t[2] = 12; break;//A-face centered + case Centering::B: t[0] = t[2] = 12; break;//B-face centered + case Centering::I: t[0] = t[1] = t[2] = 12; break;//Body centered + case Centering::F://All-face centered + t[0] = 0; t[1] = 12; t[2] = 12; p.setTrans(t); gen.insert(p); + t[0] = 12; t[1] = 0; t[2] = 12; + break; + + // case Centering::R: t[0] = 8; t[1] = t[2] = 16; break; + // case Centering::R: break;//hexagonal {8, 16, 16} for 'obverse setting' {16, 8, 16} for 'reverse setting' + case Centering::R: + if(rHex) { + t[0] = 8; t[1] = t[2] = 16; + } + + break;//hexagonal {8, 16, 16} for 'obverse setting' {16, 8, 16} for 'reverse setting' + //D cell section 4.3.5.3 + //S centering chapter 2.1 table 2.1.2.1 + //pg 16 cell relations + case Centering::H: throw std::runtime_error("H centering not yet supported");//{1/3, 2/3, 0}, transformation matricies in tabel 5.1.3.1 + } + p.setTrans(t); + gen.insert(p); + t[0] = t[1] = t[2] = 0; + + //loop over symmetry elements building generators + for(size_t i = 0; i < numEl; i++) { + + //extract rotation + if(i != indicator && 1 != sSym.sym[i].r) { + if(0 == refDirs[i][0] && 0 == refDirs[i][1] && 1 == refDirs[i][2]) {//z refDirs (can have larger rotations) + p = GenPos::Z(sSym.sym[i].r); + } else {//not z refDirs, max rotation is 2 for non-cubic groups + if (-1 == sSym.sym[i].r) { + p = GenPos::Inversion(); + } else if( 2 == sSym.sym[i].r) { + p = GenPos::Two(refDirs[i], isHexType); + } else if( 3 == std::abs( sSym.sym[i].r ) ) { + p = GenPos::Three(refDirs[i], -3 == sSym.sym[i].r); + } else { + throw std::runtime_error("invalid refDirs"); + } + } + + //extract screw + if(sSym.sym[i].s != 0) { + size_t idx; + if (1 == refDirs[i][0] && 0 == refDirs[i][1] && 0 == refDirs[i][2]) { + idx = 0; + } else if(0 == refDirs[i][0] && 1 == refDirs[i][1] && 0 == refDirs[i][2]) { + idx = 1; + } else if(0 == refDirs[i][0] && 0 == refDirs[i][1] && 1 == refDirs[i][2]) { + idx = 2; + } else { + throw std::runtime_error("invalid screw"); + } + t[idx] = 24 / std::abs(sSym.sym[i].r) * sSym.sym[i].s; + p.setTrans(t); + } + + //adjust origin with indicator if needed + //we don't need to worry about orthorhombic settings here since the indicator is just used to skip axis (always 2_0) + if(1 != sSym.sym[i].r && 3 != indicator) {//there is a rotation and this group has an indicator + //determine default application rules + int8_t wl = sym[indicator].s * 24 / sym[indicator].r;//compute translation from indicator + bool apply = (i+1) % 3 == indicator;//most families apply the indicator to the symbol before the indicator + size_t idx = 2;//most families apply the indicator to the Z axis + + //handle family specific rules + if(Family::OrthohombicABC == fam || + Family::OrthohombicBAC == fam || + Family::OrthohombicCAB == fam || + Family::OrthohombicCBA == fam || + Family::OrthohombicBCA == fam || + Family::OrthohombicACB == fam) {//orthorhombic families always apply indicator to the C axis + idx = indicator; + } else if(Family::Cubic == fam) {//cubic families have several special rules + apply = 2 == i;//cubic families always apply the indicator to the last symbol + if(apply) {//(4)32 or (-4)3m + idx = 0;//indicator is applied normally for x translation + t[1] += wl; if(t[1] > 23) t[1] -= 24;//indicator is also reverse applied to y translation + t[2] += wl; if(t[2] > 23) t[2] -= 24;//indicator is also reverse applied to z translation + } else if(2 == numEl) {//23 + throw std::logic_error("indicator for 2 symbol cubic family"); + } + } + + //apply indicator if needed + if(apply) { + t[idx] -= wl;//apply indicator + if(t[idx] < 0) t[idx] += 24;//bring translation back to [0,1) 24ths + p.setTrans(t); + } + } + + //handle 23 groups specially (2 is indicator but also needs to be included) + //special rule: location part is (-m/n, 0, 0) + if(2 == numEl && Family::Cubic == fam && //23 or m-3 type + 0 == i && 0 != sym[0].s) {//2_1 axis + t[0] -= 12; + if(t[0] < 0) t[0] += 24; + p.setTrans(t); + } + + //add rotation/screw + gen.insert(p); + t[0] = t[1] = t[2] = 0; + } + + //extract glide, see international tables 1.3.1 + if(0 != sym[i].g) { + p = GenPos::Mirror(refDirs[i], isHexType); + int8_t x = 12;//translation for d/n glide (seed with 1/2) + + switch(sym[i].g) { + case Glide::m : break;//no translation + + //axial glide types + case Glide::a : + if( (0 == refDirs[i][0] && 1 == refDirs[i][1] && 0 == refDirs[i][2]) || //y + (0 == refDirs[i][0] && 0 == refDirs[i][1] && 1 == refDirs[i][2]) ) {//z + t[0] = 12;// 1/2 a + } else throw std::runtime_error("invalid direction for a glide"); + break; + + case Glide::b : + if( (0 == refDirs[i][0] && 0 == refDirs[i][1] && 1 == refDirs[i][2]) || //z + (1 == refDirs[i][0] && 0 == refDirs[i][1] && 0 == refDirs[i][2]) ) {//x + t[1] = 12;// 1/2 b + } else throw std::runtime_error("invalid direction for b glide"); + break; + + case Glide::c : + if(Centering::R == cen && !rHex) {//handle rhombohedral setting specially + if( ( 1 == refDirs[i][0] && 0 == refDirs[i][1] && 0 == refDirs[i][2]) || // 1 00 + ( 0 == refDirs[i][0] && 1 == refDirs[i][1] && 0 == refDirs[i][2]) || // 0 10 + (-1 == refDirs[i][0] &&-1 == refDirs[i][1] && 0 == refDirs[i][2]) || // -1-10 + ( 1 == refDirs[i][0] &&-1 == refDirs[i][1] && 0 == refDirs[i][2]) || // 1-10 + ( 1 == refDirs[i][0] && 2 == refDirs[i][1] && 0 == refDirs[i][2]) || // 1 20 + (-2 == refDirs[i][0] &&-1 == refDirs[i][1] && 0 == refDirs[i][2]) ) {// -2-10 + t[0] = t[1] = t[2] = 12;// "1/2 c" == 1/2 (a+b+c) [see international table footnote for details] + } else throw std::runtime_error("invalid direction for rhombohedral c glide"); + } else { + if( (1 == refDirs[i][0] && 0 == refDirs[i][1] && 0 == refDirs[i][2]) || //x + (0 == refDirs[i][0] && 1 == refDirs[i][1] && 0 == refDirs[i][2]) || //y + (1 == refDirs[i][0] &&-1 == refDirs[i][1] && 0 == refDirs[i][2]) || //x-y + (1 == refDirs[i][0] && 1 == refDirs[i][1] && 0 == refDirs[i][2]) ) {//xy + t[2] = 12;// 1/2 c + } else throw std::runtime_error("invalid direction for c glide"); + } + break; + + //double glide + case Glide::e : + if(Centering::P == cen) throw std::runtime_error("e glide requires centered cell"); + if (0 == refDirs[i][0] && 0 == refDirs[i][1] && 1 == refDirs[i][2] ) {//z -> ( a)/2 and b/2 + t[0] = 12; p.setTrans(t); gen.insert(p); + t[0] = 0; t[1] = 12; + } else if(1 == refDirs[i][0] && 0 == refDirs[i][1] && 0 == refDirs[i][2] ) {//x -> ( b)/2 and c/2 + t[1] = 12; p.setTrans(t); gen.insert(p); + t[1] = 0; t[2] = 12; + } else if(0 == refDirs[i][0] && 1 == refDirs[i][1] && 0 == refDirs[i][2] ) {//y -> ( c)/2 and a/2 + t[2] = 12; p.setTrans(t); gen.insert(p); + t[2] = 0; t[0] = 12; + } else if(1 == refDirs[i][0] && 1 == std::abs(refDirs[i][1]) && 0 == refDirs[i][2] ) {//xy -> (a+b)/2 and c/2 + t[0] = t[1] = 12; p.setTrans(t); gen.insert(p); + t[0] = t[1] = 0; t[2] = 12; + } else if(1 == refDirs[i][0] && 1 == refDirs[i][1] && 1 == std::abs(refDirs[i][2])) {//yz -> (b+c)/2 and a/2 + t[1] = t[2] = 12; p.setTrans(t); gen.insert(p); + t[1] = t[2] = 0; t[0] = 12; + } else if(1 == std::abs(refDirs[i][0]) && 0 == refDirs[i][1] && 1 == refDirs[i][2] ) {//zx -> (c+a)/2 and b/2 + t[2] = t[0] = 12; p.setTrans(t); gen.insert(p); + t[2] = t[0] = 0; t[1] = 12; + } else { + throw std::runtime_error("invalid direction for e glide"); + } + break; + + //diamond/diagonal glide + case Glide::d : + if(!((Family::OrthohombicABC == fam && Centering::F == cen) || + (Family::OrthohombicBAC == fam && Centering::F == cen) || + (Family::OrthohombicCAB == fam && Centering::F == cen) || + (Family::OrthohombicCBA == fam && Centering::F == cen) || + (Family::OrthohombicBCA == fam && Centering::F == cen) || + (Family::OrthohombicACB == fam && Centering::F == cen) || + (Family::Tetragonal == fam && Centering::I == cen) || + (Family::Cubic == fam && Centering::P != cen))) + throw std::runtime_error("invalid lattice for diamond glide (must be orthorhombic F, tetragonal I, or cubic F/I"); + x = 6;// 1/4 + //intentional fall through (diamond and diagonal are the same except 1/4 vs 1/2 lattice vector) + case Glide::n : + if ( 0 == refDirs[i][0] && 0 == refDirs[i][1] && 1 == refDirs[i][2]) {// z -> x * ( a+-b) + t[0] = t[1] = x; + } else if( 1 == refDirs[i][0] && 0 == refDirs[i][1] && 0 == refDirs[i][2]) {// x -> x * ( b+-c) + t[1] = t[2] = x; + } else if( 0 == refDirs[i][0] && 1 == refDirs[i][1] && 0 == refDirs[i][2]) {// y -> x * ( c+-a) + t[2] = t[0] = x; + } else if( 1 == refDirs[i][0] && -1 == refDirs[i][1] && 0 == refDirs[i][2]) {//-yx -> x * (a+b+-c) + t[0] = t[1] = t[2] = x; + } else if( 0 == refDirs[i][0] && 1 == refDirs[i][1] && -1 == refDirs[i][2]) {//-zy -> x * (b+c+-a) + t[1] = t[2] = t[0] = x; + } else if(-1 == refDirs[i][0] && 0 == refDirs[i][1] && 1 == refDirs[i][2]) {//-xz -> x * (c+a+-b) + t[2] = t[0] = t[1] = x; + } else if( 1 == refDirs[i][0] && 1 == refDirs[i][1] && 0 == refDirs[i][2]) {// xy -> x * (b-a+-c) [-4 3 d type groups need +c with current origns] + t[1] = t[2] = x; t[0] = 24 - x; + } else if( 0 == refDirs[i][0] && 1 == refDirs[i][1] && 1 == refDirs[i][2]) {// yz -> x * (c-b+-a) [not used by normal space group symbols] + t[2] = t[0] = x; t[1] = 24 - x; + } else if( 1 == refDirs[i][0] && 0 == refDirs[i][1] && 1 == refDirs[i][2]) {// zx -> x * (a-c+-b) [not used by normal space group symbols] + t[0] = t[1] = x; t[2] = 24 - x; + } else { + throw std::runtime_error("invalid direction for n/d glide"); + } + break; + } + p.setTrans(t); + gen.insert(p); + t[0] = t[1] = t[2] = 0; + } + } + + //update origin and return + std::vector vGen(gen.cbegin(), gen.cend()); + for(GenPos& p : vGen) p = p.shiftOrigin(NULL == xyz ? ori : xyz); + return vGen; + } + + //@brief : get the number of non-empty symmetry elements in this symbol + //@return: number of elements - 1, 2, or 3 e.g. 1 for "P 1" and 3 for "F m 3 m" + size_t HermannMaguin::numSym() const { + const bool isEmpty[3] = { + 0 == sym[0].r && 0 == sym[0].s && 0 == sym[0].g, + 0 == sym[1].r && 0 == sym[1].s && 0 == sym[1].g, + 0 == sym[2].r && 0 == sym[2].s && 0 == sym[2].g, + }; + return isEmpty[0] ? 0 : ( isEmpty[1] ? 1 : ( isEmpty[2] ? 2 : 3 ) ); + } + + //@brief: sanity check a symbol, determine the crystal family, and unabbreviate if needed + void HermannMaguin::validate() { + //check for forbidden individual symbols + const size_t numEl = numSym(); + for(size_t i = 0; i < numEl; i++) { + if(0 != sym[i].g && sym[i].r < 0) + throw std::runtime_error("cannot compound rotoinverion in Hermann-Maguin symbol"); + if(0 != sym[i].r == 0 || 0 != sym[i].s == 0 || 0 != sym[i].g == 0) {//is the element non-empty + switch(sym[i].r) { + //normal rotations have nothing special + case 1: + case 2: + case 3: + case 4: + case 6: + case -1: + case -3: + case -4: + case -6: break; + + //handle m as -2 + case -2: + if(0 == sym[i].g || Glide::m == sym[i].g) { + sym[i].g = Glide::m; + sym[i].r = 1; + } else { + throw std::runtime_error("-2/(not m)"); + } + break; + + default: throw std::runtime_error("non-crystallographic rotation"); + } + } + } + + //now determine family and sanity check + if(0 == numEl) throw std::runtime_error("empty Hermann-Maguin symbol"); + else if(1 == numEl) {//single symbols are easy + switch(std::abs(int(sym[0].r))) { + case 2 ://intentional fall through + case 1 ://fall through for 1/m + if(1 == std::abs(sym[0].r) && 0 == sym[0].g) {//handle 1/m + fam = Family::Triclinic; + } else { + //unabbreviate with b unique axis + fam = Family::MonoclinicB; + sym[1] = sym[0]; + sym[2].r = 1; + sym[0] = sym[2]; + } + break; + + //other groups are handled trivially (cell check if after family determination) + case 3 : fam = Family::Trigonal ; break; + case 4 : fam = Family::Tetragonal; break; + case 6 : fam = Family::Hexagonal ; break; + default: throw std::logic_error("non-crystallographic rotation"); + } + } else {//2 or 3 symbols + if(3 == std::abs(sym[1].r)) {//handle cubic specially + fam = Family::Cubic; + if(0 != sym[1].s || 0 != sym[1].g) throw std::runtime_error("cubic Hermann-Maguin symbol _3_ not have screw or glide with 3"); + fam = Family::Cubic; + const bool m0 = 0 != sym[0].g;//point group is m__ + + if(2 == numEl) {// 23 or m-3 + //uncompress m and sanity check rotation + if(1 == sym[0].r && m0) sym[0].r = 2;// m3 => 2/m3 + else if(2 != sym[0].r) throw std::runtime_error("2 element Hermann-Maguin symbol _3 must be point group 23 or m-3"); + + // 3 => -3 and handle space groups 205 and 206 specially + if(m0) { + sym[1].r = -3;// make sure we have m-3 not m3 + if(Glide::a == (Glide)sym[0].g) sym[0].s = 1;//for these 2 groups only 2_1/a is abbreviated a + } + } else {// 432, -43m, or m-3m + //uncompress m's and sanity check rotations + const bool m2 = 0 != sym[2].g;//point group is m__ + if(m0 && m2) { + if(1 == sym[0].r) sym[0].r = 4;// m 3 m => 4/m 3 m + if(1 == sym[2].r) sym[2].r = 2;// m 3 m => m 3 2/m + else if(2 != sym[2].r) throw std::runtime_error("3 element Hermann-Maguin symbol m3m must be m 3 2/m"); + sym[1].r = -3;//m 3 m => m -3 m + } + + //make sure we fall into one of 3 possible point groups + const bool is4 = 4 == sym[0].r; + const bool isb4 = -4 == sym[0].r; + if (is4 && m0 && m2) {// 4/m -3 2/m (2 ensured from previous test) + } else if(isb4 && !m0 && m2 && 1 == sym[2].r) {//-43m + } else if(is4 && !m0 && !m2 && 2 == sym[2].r) {//432 + } else throw std::runtime_error("3 element Hermann-Maguin symbol _3_ must be 432, -43m or m3m"); + } + } else { + //2 or 3 symbols with |2nd rotation| != 3 + for(size_t i = 1; i < numEl; i++) { + switch(sym[i].r) { + case 1 : + case 2 : break; + default: throw std::runtime_error("secondary and tertiary rotational order must be 1 or 2 for non cubic groups"); + } + } + + //this should be monoclinic, orthorhombic, tetragonal, trigonal, or hexagonal + switch(std::abs(int(sym[0].r))) { + case 1 : + case 2 : {//monoclinic or orthorhombic + if(2 == numEl) throw std::runtime_error("monoclinic/orthorhombic symbols must have (1 or 3)/3 elements respectively"); + size_t num1 = ( (1 == sym[0].r && 0 == sym[0].g ) ? 1 : 0 ) + + ( (1 == sym[1].r && 0 == sym[1].g ) ? 1 : 0 ) + + ( (1 == sym[2].r && 0 == sym[2].g ) ? 1 : 0 ); + if(3 == num1 || 1 == num1) throw std::runtime_error("unexpected Hermann-Maguin symbol (1 or 3 '1's"); + if(2 == num1) {//2, m, or 2/m + if (1 == sym[0].r && 0 == sym[0].g ) + fam = Family::MonoclinicA; + else if(1 == sym[1].r && 0 == sym[1].g ) + fam = Family::MonoclinicB; + else// (1 == sym[2].r && 0 == sym[2].g ) + fam = Family::MonoclinicC; + } else {//0 == num1 + fam = Family::OrthohombicABC;//this could be relaxed in an attempt to determine the setting + size_t numG = (0 != sym[0].g ? 1 : 0) + + (0 != sym[1].g ? 1 : 0) + + (0 != sym[2].g ? 1 : 0); + size_t num2 = (2 == sym[0].r ? 1 : 0) + + (2 == sym[1].r ? 1 : 0) + + (2 == sym[2].r ? 1 : 0); + if(3 == num2 && 0 == numG) { + //222 + } else if(1 == num2 && 2 == numG) { + //mm2 + } else if(3 == num2 && 3 == numG) { + //mmm (already full symbol) + } else if(0 == num2 && 3 == numG) { + //mmm (abbreviated) + sym[0].r = 2; + sym[1].r = 2; + sym[2].r = 2; + } else { + throw std::runtime_error("unexpected mix of 2 fold and mirrors in orthorhombic symbol"); + } + } + } break; + + case 3 : fam = Family::Trigonal; + // sanity check length + if( !( (Centering::P == cen && 3 == numEl) || + (Centering::R == cen && 2 == numEl) ) ) throw std::runtime_error("P/R hexagonal symbols must have 1 or 3/2 elements"); + + if(sym[0].r < 0) {//-3m1 or -31m + if(0 != sym[1].g) { + if(1 == sym[1].r) sym[1].r = 2; + } else if(0 != sym[2].g) { + if(1 == sym[2].r) sym[2].r = 2; + } + } else { + //321, 312, 3m1, or 31m + } + break;//3 + + case 4 : fam = Family::Tetragonal; + if(2 == numEl) throw std::runtime_error("tetragonal symbols must have 1 or 3 elements"); + if(sym[0].r < 0) { + //-42m or -4m2 + } else {//422, 4mm, or 4/mmm + if(0 != sym[0].g) {//4/mmm + if(1 == sym[1].r) sym[1].r = 2; + if(1 == sym[2].r) sym[2].r = 2; + } else { + //422 or 4mm + } + } + break; + + case 6 : fam = Family::Hexagonal; + if(2 == numEl) throw std::runtime_error("hexagonal symbols must have 1 or 3 elements"); + if(sym[0].r < 0) {//-6/mmm, -6m2, or -62m + if(0 == sym[0].g) { + //-6m2, or -62m + } else {//-6/mmm + if(1 == sym[1].r) sym[1].r = 2; + if(1 == sym[2].r) sym[2].r = 2; + } + } else { + //622 or 6mm + } + break; + + default: throw std::logic_error("non-crystallographic rotation"); + } + } + } + + //now that we have the family also check the centering + if(P == cen) return;//all lattices can be primitive + switch(fam) { + case Family::Triclinic : throw std::runtime_error("triclinic groups must be P centered"); + case Family::MonoclinicB : + case Family::MonoclinicBarB: + case Family::MonoclinicC : + case Family::MonoclinicBarC: + case Family::MonoclinicA : + case Family::MonoclinicBarA://this could be made more strict by splitting out cases + switch(cen) { + case Centering::A: + case Centering::B: + case Centering::C: break; + default: throw std::runtime_error("monoclinic groups must be P, A, B, or C centered"); + } + break; + + case Family::OrthohombicABC: + case Family::OrthohombicBAC: + case Family::OrthohombicCAB: + case Family::OrthohombicCBA: + case Family::OrthohombicBCA: + case Family::OrthohombicACB://this could be made more strict by splitting out cases + switch(cen) { + case Centering::A: + case Centering::B: + case Centering::C: + case Centering::I: + case Centering::F: break; + default: throw std::runtime_error("monoclinic groups must be P, A, B, C, I, or F centered"); + } + break; + + case Family::Tetragonal : if(Centering::I != cen) std::runtime_error("tetragonal groups must be P or I centered (C/F not yet implemented)"); break; + + case Family::Trigonal : if(Centering::R != cen) std::runtime_error("trigonal groups must be P or R centered (H not yet implemented)"); break; + + case Family::Hexagonal : throw std::runtime_error("hexagonal groups must be P centered (H not yet implemented)"); + + case Family::Cubic : + switch(cen) { + case Centering::I: break;//TODO: check consistency w/unique axis + case Centering::F: break;//TODO: check consistency w/unique axis + default: throw std::runtime_error("cubic groups must be P, I, or F centered"); + } + break; + } + } + + //@brief : change axis to new setting (monoclinic groups only) + //@param axis: new axis, it and fam must be one of: + // Family::MonoclinicB ,//ab̲c + // Family::MonoclinicBarB,// cb̲̅a + // Family::MonoclinicC ,//abc̲ + // Family::MonoclinicBarC,// bac̲̅ + // Family::MonoclinicA ,//a̲bc + // Family::MonoclinicBarA,// a̲̅cb + //@param cell: cell choice (must be one of 1, 2, or 3) + //@return : updated symbol with new cell + HermannMaguin HermannMaguin::changeMonoCell(const size_t cell, const Family axis) const { + if(!(1 == cell || 2 == cell || 3 == cell)) throw std::runtime_error("changeMonoCell new cell must be (1, 2 or 3)"); + const size_t newCell = cell - 1;//convert to 0 indexing + + //the centering permutation is the same for all non-primitive monoclinic groups + static const int_fast8_t CenABCI[6][3] = { + {Centering::C, Centering::A, Centering::I},//unique axis b cell choice 1, 2, 3 + {Centering::A, Centering::C, Centering::I},//unique axis -b cell choice 1, 2, 3 + {Centering::A, Centering::B, Centering::I},//unique axis c cell choice 1, 2, 3 + {Centering::B, Centering::A, Centering::I},//unique axis -c cell choice 1, 2, 3 + {Centering::B, Centering::C, Centering::I},//unique axis a cell choice 1, 2, 3 + {Centering::C, Centering::B, Centering::I},//unique axis -a cell choice 1, 2, 3 + }; + + //the glide permutation is almost the same for all groups + //this is the permutation for groups 7, 13, 14 and the preferred permutation for groups 9 and 15 + //the preferred glide for groups 8 and 12 is m but this is the alternate glide for cell choices (3,1,2) + //the alternate glide for groups 9 and 15 is this for cell choices (2,3,1) + static const int_fast8_t GldABCN[6][3] = { + {Glide::c, Glide::n, Glide::a},//unique axis b cell choice 1, 2, 3 + {Glide::a, Glide::n, Glide::c},//unique axis -b cell choice 1, 2, 3 + {Glide::a, Glide::n, Glide::b},//unique axis c cell choice 1, 2, 3 + {Glide::b, Glide::n, Glide::a},//unique axis -c cell choice 1, 2, 3 + {Glide::b, Glide::n, Glide::c},//unique axis a cell choice 1, 2, 3 + {Glide::c, Glide::n, Glide::b},//unique axis -a cell choice 1, 2, 3 + }; + + //convert new/old family to index + static const Family FamABC[6] = { + MonoclinicB ,// b unique + MonoclinicBarB,//-b unique + MonoclinicC ,// c unique + MonoclinicBarC,//-c unique + MonoclinicA ,// a unique + MonoclinicBarA //-a unique + }; + const size_t uOld = std::distance(FamABC, std::find(FamABC, FamABC+6, fam )); + const size_t uNew = std::distance(FamABC, std::find(FamABC, FamABC+6, axis)); + if(uOld > 5) throw std::runtime_error("cannot changeMonoCell of non-monoclinic group"); + if(uNew > 5) throw std::runtime_error("cannot changeMonoCell to non-monoclinic group"); + + //copy symbol and get unique axis + HermannMaguin hm(*this);//copy symbol + hm.fam = axis;//update unique axis + static const size_t Idx2[6] = {1, 1, 2, 2, 0, 0}; + AxisSym& sym2 = hm.sym[Idx2[uOld]]; + + //next update centering/glide handling special cases (groups 8, 9, 12, and 15) + size_t oldCell = newCell; + if(Centering::P != cen) {//this is space group 5, 8, 9, 12, or 15 + //determine cell choice from centering and then update + oldCell = std::distance(CenABCI[uOld], std::find(CenABCI[uOld], CenABCI[uOld]+3, cen)); + if(3 == oldCell) throw std::runtime_error("failed to determine monoclinic cell choice from centering"); + hm.cen = CenABCI[uNew][newCell];//update centering + + //now update glide + if(0 == sym2.g || Glide::m == sym2.g) { + //this is space group 5 (0) or default symbol for 8 (m), we only needed to update centering + } else { + //group 8 (alternate symbol), 9, 12, or 15 + const size_t idx = (3 + oldCell - std::distance(GldABCN[uOld], std::find(GldABCN[uOld], GldABCN[uOld]+3, sym2.g)) ) % 3;//get permutation + if(3 == idx) throw std::runtime_error("failed to determine monoclinic cell choice from centering + glide");//sanity check glide type + sym2.g = GldABCN[uNew][(newCell+idx)%3];//update glide symbol + + //further sanity check symbol for groups 12 / 15 + if(2 == sym2.r) { + if(0 == sym2.s && 0 == idx) { + //space group 15, normal symbol + } else if(1 == sym2.s) { + //space group 12/15 alternate symbol + } else { + throw std::runtime_error("unexpected monoclinic rotation + glide combination");//sanity check glide type + } + } + } + } else if(0 != sym2.g) {//only glide needs to be (potentially) updated + switch(sym2.g) {//check if the glide type needs to be updated + case Glide::m: break; + case Glide::a: + case Glide::b: + case Glide::c: + case Glide::n: { + //update glide type + oldCell = std::distance(GldABCN[uOld], std::find(GldABCN[uOld], GldABCN[uOld]+3, sym2.g));//0 indexed => 0, 1, or 2 + if(3 == oldCell) throw std::runtime_error("failed to determine monoclinic cell choice from glide"); + sym2.g = GldABCN[uNew][newCell]; + } break; + default: throw std::runtime_error("unexpected monoclinic glide type"); + } + } else { + //this symbol only has 1 cell choice + } + + //now that we have the old and new cell type build the transformation matrix between them (see ITA 5.1.3.1) + if(oldCell != newCell) { + const bool cyc = ((oldCell + 1) % 3) == newCell;// true/false for 1 -> 2 -> 3 -> 1 / 1 -> 3 -> 2 -> 1 + const bool pos = uOld % 2 == 0;//true/false for +unique axis / mirrored (negative) unique axis + size_t idx = Idx2[uOld] * 2 + (cyc == pos ? 0 : 1); + switch(idx) {//appy the transformation + case 0: hm.ori[1] = -(ori[1] + ori[2]); hm.ori[2] = ori[1]; break;// +/-a unique : x ,-y-z, y + case 1: hm.ori[2] = -(ori[1] + ori[2]); hm.ori[1] = ori[2]; break;// +/-a unique : x , z , -y-z + case 2: hm.ori[2] = -(ori[2] + ori[0]); hm.ori[0] = ori[2]; break;// +/-b unique : z , y ,-z-x + case 3: hm.ori[0] = -(ori[2] + ori[0]); hm.ori[2] = ori[0]; break;// +/-b unique : -z-x, y , x + case 4: hm.ori[0] = -(ori[0] + ori[1]); hm.ori[1] = ori[0]; break;// +/-c unique : -x-y, x , z + case 5: hm.ori[1] = -(ori[0] + ori[1]); hm.ori[0] = ori[1]; break;// +/-c unique : y ,-x-y, z + } + const size_t idxSum = ( ( (idx+1) / 2 ) + 1 ) % 3; + while(hm.ori[idxSum] < 0) hm.ori[idxSum] += 24;//bring the summed element back to [0,24) + } + + //now that the symbol elements and origin have been updated, shuffle as needed to change unique axis + if(uOld != uNew) {//some sort of change + const bool mir = (uOld % 2) != (uNew % 2);//unique axis is mirrored + const bool shuf = Idx2[uOld] != Idx2[uNew];//unique axis is moved + + //shuffle symbols as needed + if(shuf) {//shuffle axis + std::rotate(hm.sym, hm.sym + (Idx2[uOld] + 3 - Idx2[uNew]) % 3, hm.sym + 3); + std::rotate(hm.ori, hm.ori + (Idx2[uOld] + 3 - Idx2[uNew]) % 3, hm.ori + 3); + } + + //negate unique axis if needed + if(mir) {//this only needs to be applied to the origin + const size_t idx = Idx2[uNew];//new unique axis + hm.ori[idx] = (24 - hm.ori[idx]) % 24;//negate c axis + std::swap(hm.ori[(idx+1) % 3], hm.ori[(idx+2) % 3]);//swap non c axis + } + } + return hm; + } + + //@brief : change axis to new setting (orthorhombic groups only) + //@param axis: new axis, it and fam must be one of: + // Family::OrthohombicABC == abc + // Family::OrthohombicBAC == bac̅ + // Family::OrthohombicCAB == cab + // Family::OrthohombicCBA == c̅ba + // Family::OrthohombicBCA == bca + // Family::OrthohombicACB == ac̅b + //@return : updated symbol with new cell + HermannMaguin HermannMaguin::changeOrthoAxis(const Family axis) const { + //first get old and new type + int curC, newC; + switch(fam) { + case Family::OrthohombicABC: curC = 3; break; + case Family::OrthohombicBAC: curC =-3; break; + case Family::OrthohombicCAB: curC = 1; break; + case Family::OrthohombicCBA: curC =-1; break; + case Family::OrthohombicBCA: curC = 2; break; + case Family::OrthohombicACB: curC =-2; break; + default: throw std::runtime_error("cannot changeOrthoAxis of non-orthorhombic group"); + } + switch(axis) { + case Family::OrthohombicABC: newC = 3; break; + case Family::OrthohombicBAC: newC =-3; break; + case Family::OrthohombicCAB: newC = 1; break; + case Family::OrthohombicCBA: newC =-1; break; + case Family::OrthohombicBCA: newC = 2; break; + case Family::OrthohombicACB: newC =-2; break; + default: throw std::runtime_error("cannot changeOrthoAxis to non-orthorhombic group"); + } + + //next build copy of current symbol and update + HermannMaguin hm(*this); + hm.fam = axis; + const int aCurC = std::abs(curC); + const int aNewC = std::abs(newC); + int delta = ( aNewC + (3-aCurC) ) % 3; + const bool mir = curC * newC < 0; + + //update centering if needed + switch(cen) { + case Centering::A: + case Centering::B: + case Centering::C: { + int x = cen - Centering::A;//A,B,C centerin to 0,1,2 + int dx = ( delta + ( mir ? 7 - aCurC - x : x ) ) % 3;//compute new centering type as 0,1,2 + hm.cen = Centering::A + dx;;//convert back to A,B,C + } + default: break; + } + + //bring glide types back to abc + for(size_t i = 0; i < 3; i++) {//loop over symmetry elements adjusting + switch(sym[i].g) { + case Glide::a: + case Glide::b: + case Glide::c: { + int x = sym[i].g - Glide::a;//a,b,c glide to 0,1,2 + int dx = ( delta + ( mir ? 7 - aCurC - x : x ) ) % 3;//compute new glide type as 0,1,2 + hm.sym[i].g = Glide::a + dx;//convert back to a,b,c + } + default: break; + } + } + + //update axis + if(delta != 0) std::rotate(hm.sym, hm.sym + 3 - delta, hm.sym + 3);//c axis moves -> rotate + if(mir) std::swap(hm.sym[aNewC % 3], hm.sym[(aNewC + 1) % 3]);//sign mismatch -> swap non c axis + + //finally update origin shift + if(delta != 0) std::rotate(hm.ori, hm.ori + 3 - delta, hm.ori + 3);//c axis moves -> rotate + if(mir) { + hm.ori[aNewC-1] = (24 - hm.ori[aNewC-1]) % 24;//sign mismatch -> negate c axis + std::swap(hm.ori[aNewC % 3], hm.ori[(aNewC + 1) % 3]);//sign mismatch -> swap non c axis + } + return hm; + } +} + +#endif//_HM_H_ diff --git a/include/xtal/orientation_map.hpp b/include/xtal/orientation_map.hpp new file mode 100644 index 0000000..cd7900b --- /dev/null +++ b/include/xtal/orientation_map.hpp @@ -0,0 +1,686 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019, William C. Lenthe * + * All rights reserved. * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _OrientationMap_H_ +#define _OrientationMap_H_ + +#include +#include + +#include "phase.hpp" +#include "quaternion.hpp" + +#define XTAL_USE_TSL +#define XTAL_USE_HKL + +#ifdef XTAL_USE_TSL + #include "vendor/tsl.hpp" +#endif + +#ifdef XTAL_USE_HKL + #include "vendor/hkl.hpp" +#endif + +namespace ebsd { + //a subset of the complete geometry (we're lucky if the vendors give us this much) + template + struct Calibration { + Real sTlt ;//sample tilt in degrees + Real cTlt ;//camera tilt in degrees + Real wd ;//sample working distance in mm + Real kv ;//accelerating voltage in kV + Real xStar;//tsl style pattern center x (in fractional detector widths from center) + Real yStar;//tsl style pattern center y (in fractional detector heights from center) + Real zStar;//tsl style pattern center z (in fractional detector widths from interaction point) + + //@brief: construct an uninitialized calibration + Calibration() : sTlt(NAN), cTlt(NAN), wd(NAN), kv(NAN), xStar(NAN), yStar(NAN), zStar(NAN) {} + + }; +} + +namespace xtal { + //a 2D orientation map on a square grid + template + struct OrientationMap { + //scan conditions + uint32_t width ;//scan width in pixels + uint32_t height ;//scan height in pixels + Real xStep ;//width of pixel in microns + Real yStep ;//height of pixel in microns + ebsd::Calibration calib ;//detector geometry + + //meta data + std::string owner ;//name of person who collected the data + std::string name ;//description of sample, project, etc + + //possible phases + std::vector< Phase > phsList;//list of phases + + //pixel information + std::vector< uint_fast8_t> phase ;//index of phase at each point + std::vector< Quat > qu ;//orientation at each point (as quaternions [w,x,y,z]) + std::vector< Real > metric ;//scalar indexing quality measure at each point (e.g. CI for tsl) + std::vector< Real > imQual ;//scalar pattern quality measure at each point (e.g. IQ) + + //@brief : create an empty orientation map + //@param w : width of scan in pixels + //@param h : height of scan in pixels + //@param dx: pixel width in microns + //@param dy: pixel height in microns + OrientationMap(const uint32_t w, const uint32_t h, const Real dx = 1, const Real dy = 1); + + #ifdef XTAL_USE_TSL + //@brief : extract an orientation map from a tsl scan + //@param tsl: tsl scan to extract from + OrientationMap(const tsl::OrientationMap& om); + + //@brief : convert an orientation map to a tsl scan format + //@return: tsl scan + tsl::OrientationMap toTSL() const; + #endif + + #ifdef XTAL_USE_HKL + //@brief : extract an orientation map from an hkl scan + //@param hkl: hkl scan to extract from + OrientationMap(const hkl::OrientationMap& om); + + //@brief : convert an orientation map to a hkl scan format + //@return: hkl scan + hkl::OrientationMap toHKL() const; + #endif + + //@brief : read an orientation map from a file + //@param fileName: name of file to read from + //@param aux : auxilary information (for hdf5 datasets this is the path to the dataset) + OrientationMap(const std::string fileName, const std::string aux = "") {read(fileName, aux);} + + //@brief : read an orientation map from a file + //@param fileName: name of file to read from + //@param aux : auxilary information (for hdf5 datasets this is the scan name) + void read(const std::string fileName, const std::string aux = ""); + + //@brief : write an orientation map to a file + //@param fileName: name of file to write to + void write(const std::string fileName) const; + + //@brief : write an orientation map to an hdf5 group + //@param grp: h5 group to write to + void writeH5(H5::Group grp) const; + + //@brief: build an ipf color map + //@param rgb : location to write red, green, blue (, alpha) values + //@param refDir: reference direction (sample frame) + //@param h2r: hsl2rgb like coloring function to use with h, s, and l in [0,1] and output as [0,1] rgb: void(Real const * const hsl, Real * const rgb) + //@param alpha : true to write an RGBA image, false for RGB + void ipfColor(uint8_t * const rgb, Real const * const refDir, std::function h2r, const bool alpha = false) const; + + //@brief: build an ipf color map + //@param refDir: reference direction (sample frame) + //@param h2r: hsl2rgb like coloring function to use with h, s, and l in [0,1] and output as [0,1] rgb: void(Real const * const hsl, Real * const rgb) + //@param alpha : true to write an RGBA image, false for RGB + //@return : red, green, blue (, alpha) values + std::vector ipfColor(Real const * const refDir, std::function h2r, const bool alpha = false) const; + }; +} + +#include + +#include "rotations.hpp" +#include "constants.hpp" + +namespace xtal { + namespace detail { + //@brief : check if a file name is a hdf5 type + //@param fileName: name of file to check + //@return : true if extension is hdf, h5, or hdf5 + bool isH5(std::string fileName) { + size_t pos = fileName.find_last_of(".");//find the last '.' in the name + if(std::string::npos == pos) return false;//handle files with no extension + std::string ext = fileName.substr(pos+1);//extract the file extension + std::transform(ext.begin(), ext.end(), ext.begin(), [](char c){return std::tolower(c);});//convert to lowercase + + if (0 == ext.compare("hdf" )) return true; + else if(0 == ext.compare("hdf5")) return true; + else if(0 == ext.compare("h5" )) return true; + else return false; + } + } + + //@brief : create an empty orientation map + //@param w : width of scan in pixels + //@param h : height of scan in pixels + //@param dx: pixel width in microns + //@param dy: pixel height in microns + template + OrientationMap::OrientationMap(const uint32_t w, const uint32_t h, const Real dx, const Real dy) + : width (w ), height(h ), + xStep (dx), yStep (dy), + owner ("unknown"), + name ("" ), + phase (w * h, 0 ), + qu (w * h, Quat::Zero()), + metric(w * h, Real(0) ), + imQual(w * h, Real(0) ) {} + +#ifdef XTAL_USE_TSL + //@brief : extract an orientation map from a tsl scan + //@param tsl: tsl scan to extract from + template + OrientationMap::OrientationMap(const tsl::OrientationMap& om) { + //hex isn't allowed (could convert to square in the future) + if(tsl::GridType::Square != om.gridType) throw std::runtime_error("hexagonal grids aren't supported"); + + //extract header data + width = om.nColsOdd ;//or om.nColsEven (should be the same for square grids) + height = om.nRows ; + xStep = om.xStep ; + yStep = om.yStep ; + calib.kv = NAN ; + calib.sTlt = om.sampTlt ; + calib.cTlt = om.camTlt ; + calib.wd = om.workingDistance; + calib.xStar = om.xStar ; + calib.yStar = om.yStar ; + calib.zStar = om.zStar ; + owner = om.operatorName ; + name = om.sampleId ; + if(!om.scanId.empty()) name += ": " + om.scanId; + + //extract phases + for(const tsl::Phase& p : om.phaseList) { + Phase xp; + std::copy(p.lat, p.lat+6, xp.lat); + for(size_t i = 0; i < 3; i++) xp.lat[i] /= 10;//angstroms -> nm + xp.name = p.name;//currently we loose formula and info data + xp.pg = PointGroup::FromTSL(p.sym); + //currently we lose hkl families, elastic constants, and cetegories + phsList.push_back(xp); + } + + //extract phases, orientations, and quality measure + phase .resize(width * height); + qu .resize(width * height); + metric.resize(width * height); + imQual.resize(width * height); + std::copy(om.phase.cbegin(), om.phase.cend(), phase .begin()); + std::copy(om.ci .cbegin(), om.ci .cend(), metric.begin());//could also use fit + std::copy(om.iq .cbegin(), om.iq .cend(), imQual.begin()); + float const * pEu = &om.eu[0]; + Real eu[3]; + for(Quat& q : qu) { + std::copy(pEu, pEu + 3, eu);//convert eu to Real + + //undo tsl convention + // eu[0] = std::fmod(eu[0]+Constants::pi_2, Constants::pi2); + + pEu += 3;//increment euler angle for next iteration + eu2qu(eu, q.data());//convert to quaternion + } + } + + //@brief : convert an orientation map to a tsl scan format + //@return: tsl scan + template + tsl::OrientationMap OrientationMap::toTSL() const { + //create map + tsl::OrientationMap om; + om.gridType = tsl::GridType::Square; + + //copy camera calibration + + om.sampTlt = (float) calib.sTlt ; + om.camTlt = (float) calib.cTlt ; + om.xStar = (float) calib.xStar; + om.yStar = (float) calib.yStar; + om.zStar = (float) calib.zStar; + om.workingDistance = (float) calib.wd ; + + //copy header info + om.xStep = (float) xStep ; + om.yStep = (float) yStep ; + om.nColsOdd = width ; + om.nColsEven = width ; + om.nRows = height; + om.operatorName = owner ; + om.sampleId = name ; + + //copy phase data + om.phaseList.resize(phsList.size()); + for(size_t i = 0; i < phsList.size(); i++) { + om.phaseList[i].num = i+1; + om.phaseList[i].name = phsList[i].name; + om.phaseList[i].sym = phsList[i].pg.tslNum(); + for(size_t j = 0; j < 6; j++) om.phaseList[i].lat[j] = (float)phsList[i].lat[j]; + for(size_t j = 0; j < 3; j++) om.phaseList[i].lat[j] *= 10;//nm to angstroms + } + + //allocate scan data and copy + om.allocate(8);//we dont need SEM or fit + std::copy (phase .cbegin(), phase .cend(), om.phase.begin()); + std::transform(metric.cbegin(), metric.cend(), om.ci .begin(), [](const Real& r){return (float)r;}); + std::transform(imQual.cbegin(), imQual.cend(), om.iq .begin(), [](const Real& r){return (float)r;}); + + //convert quats to eulers + Real eu[3]; + float * pEu = &om.eu[0]; + for(const Quat& q : qu) { + qu2eu(q.data(), eu);//convert to euler angles + for(size_t i = 0; i < 3; i++) pEu[i] = (float)eu[i];//convert eu to float + pEu += 3;//increment euler angle for next iteration + } + + //build x/y + for(size_t i = 0; i < width; i++) om.x[i] = (float)(xStep * i); + for(size_t j = 0; j < height; j++) { + std::copy(om.x.begin(), om.x.begin() + width, om.x.begin() + j * width);//copy x from first row + std::fill(om.y.begin() + j * width, om.y.begin() + (j+1) * width, (float)(yStep * j));//fill y with current value + } + return om; + } +#endif//XTAL_USE_TSL + +#ifdef XTAL_USE_HKL + + //@brief : extract an orientation map from an hkl scan + //@param hkl: hkl scan to extract from + template + OrientationMap::OrientationMap(const hkl::OrientationMap& om) { + //hex isn't allowed (could convert to square in the future) + if(0 != om.jobMode.compare("Grid")) throw std::runtime_error("hexagonal grids aren't supported"); + + //extract header data + width = om.xCells ; + height = om.yCells ; + xStep = om.xStep ; + yStep = om.yStep ; + calib.sTlt = om.angle ; + calib.kv = om.kv ; + owner = om.author ; + name = om.project; + + //extract phases + for(const hkl::Phase& p : om.phaseList) { + Phase xp; + std::copy(p.lat, p.lat+6, xp.lat); + for(size_t i = 0; i < 3; i++) xp.lat[i] /= 10;//angstroms -> nm + xp.name = p.name; + xp.pg = PointGroup(p.space);//convert from space group to point group (some loss of data) + phsList.push_back(xp); + } + + //extract phases, orientations, and quality measure + phase .resize(width * height); + qu .resize(width * height); + metric.resize(width * height); + imQual.resize(width * height); + std::copy (om.phase.cbegin(), om.phase.cend(), phase .begin()); + std::for_each(phase . begin(), phase . end(), [](uint_fast8_t& i){--i;});//hkl phases are 1 indexed + std::copy (om.err .cbegin(), om.err .cend(), metric.begin());//could also use MAD + std::copy (om.bc .cbegin(), om.bc .cend(), imQual.begin());//could also use BS + float const * pEu = &om.eu[0]; + Real eu[3]; + for(Quat& q : qu) { + std::copy(pEu, pEu + 3, eu);//convert eu to Real + for(size_t i = 0; i < 3; i++) eu[i] *= Constants::dg2rd;//convert from degrees to radians + pEu += 3;//increment euler angle for next iteration + eu2qu(eu, q.data());//convert to quaternion + } + } + + //@brief : convert an orientation map to a hkl scan format + //@return: hkl scan + template + hkl::OrientationMap OrientationMap::toHKL() const { + //create map + hkl::OrientationMap om; + om.jobMode = "Grid"; + + //copy header info + om.xCells = width ; + om.yCells = height ; + om.zCells = 1 ; + om.xStep = (float) xStep ; + om.yStep = (float) yStep ; + om.zStep = 0 ; + om.angle = (float) calib.sTlt; + om.kv = (float) calib.kv ; + om.author = owner ; + om.project = name ; + om.euStr = "Euler angles refer to Sample Coordinate system (CS0)!"; + + //copy phase data + for(const Phase& xp : phsList) { + hkl::Phase p; + for(size_t i = 0; i < 6; i++) p.lat[i] = (float)xp.lat[i]; + for(size_t i = 0; i < 3; i++) p.lat[i] *= 10;//nm to angstroms + p.name = xp.name; + p.laue = xp.pg.hklNum (); + p.space = xp.pg.symmorphic();//get lowest symmetry space group of point group + om.phaseList.push_back(p); + } + + //allocate scan data and copy + om.allocate( hkl::OrientationMap::CTF_Phase + | hkl::OrientationMap::CTF_Error + | hkl::OrientationMap::CTF_Euler + | hkl::OrientationMap::CTF_X + | hkl::OrientationMap::CTF_Y); + std::transform(phase .cbegin(), phase .cend(), om.phase.begin(), [](const uint_fast8_t& i){return i+1;});//0->1 indexed + std::transform(metric.cbegin(), metric.cend(), om.err .begin(), [](const Real & r){return (float)r ;});//real->float + std::transform(imQual.cbegin(), imQual.cend(), om.bc .begin(), [](const Real & r){return (float)r ;});//real->float + + //convert quats to eulers + Real eu[3]; + float * pEu = &om.eu[0]; + for(const Quat& q : qu) { + qu2eu(q.data(), eu);//convert to euler angles + for(size_t i = 0; i < 3; i++) pEu[i] = (float) (eu[i] * Constants::rd2dg);//convert from radians to degrees + pEu += 3;//increment euler angle for next iteration + } + + //build x/y + for(size_t i = 0; i < width; i++) om.x[i] = (float)(xStep * (i+1)); + for(size_t j = 0; j < height; j++) { + std::copy(om.x.begin(), om.x.begin() + width, om.x.begin() + j * width);//copy x from first row + std::fill(om.y.begin() + j * width, om.y.begin() + (j+1) * width, (float)(yStep * (j+1)));//fill y with current value + } + return om; + } +#endif + + //@brief : read an orientation map from a file + //@param fileName: name of file to read from + //@param aux : auxilary information (for hdf5 datasets this is the scan name) + template + void OrientationMap::read(const std::string fileName, const std::string aux) { + //first try to read as h5 dot product file + if(H5::H5File::isHdf5(fileName)) { + //open file and check if it has an "EMheader" group + H5::H5File file(fileName.c_str(), H5F_ACC_RDONLY); + int idx = 0; + bool exists = false; + file.iterateElems(".", &idx, [](int, const char* nm, void* pB)->int{ + if(0 == std::string("EMheader").compare(nm)) *(bool*)pB = true; + return 0; + }, &exists); + + //check for EMsoft format before vendors + if(exists) {//this is an EMsoft file + H5::Group grp = file.openGroup(aux + "/EBSD");//open the scan in question + H5::Group hdr = grp.openGroup("Header"); + H5::Group clb = hdr.openGroup("Pattern Center Calibration"); + + //read header + float vf; + hdr.openDataSet("nColumns" ).read(&width , H5::PredType::NATIVE_UINT32 , H5::DataSpace(H5S_SCALAR)); + hdr.openDataSet("nRows" ).read(&height, H5::PredType::NATIVE_UINT32 , H5::DataSpace(H5S_SCALAR)); + hdr.openDataSet("Step X" ).read(&vf , H5::PredType::NATIVE_FLOAT , H5::DataSpace(H5S_SCALAR)); xStep = (Real) vf; + hdr.openDataSet("Step Y" ).read(&vf , H5::PredType::NATIVE_FLOAT , H5::DataSpace(H5S_SCALAR)); yStep = (Real) vf; + hdr.openDataSet("Operator" ).read( owner , H5::StrType(0, H5T_VARIABLE), H5::DataSpace(H5S_SCALAR)); + hdr.openDataSet("Scan ID" ).read( name , H5::StrType(0, H5T_VARIABLE), H5::DataSpace(H5S_SCALAR)); + hdr.openDataSet("Sample Tilt" ).read(&vf , H5::PredType::NATIVE_FLOAT , H5::DataSpace(H5S_SCALAR)); calib.sTlt = (Real) vf; + hdr.openDataSet("Working Distance").read(&vf , H5::PredType::NATIVE_FLOAT , H5::DataSpace(H5S_SCALAR)); calib.wd = (Real) vf; + clb.openDataSet("x-star" ).read(&vf , H5::PredType::NATIVE_FLOAT , H5::DataSpace(H5S_SCALAR)); calib.xStar = (Real) vf; + clb.openDataSet("y-star" ).read(&vf , H5::PredType::NATIVE_FLOAT , H5::DataSpace(H5S_SCALAR)); calib.yStar = (Real) vf; + clb.openDataSet("z-star" ).read(&vf , H5::PredType::NATIVE_FLOAT , H5::DataSpace(H5S_SCALAR)); calib.zStar = (Real) vf; + + //read phases + hdr = hdr.openGroup("Phase"); + const size_t numPhase = hdr.getNumObjs(); + phsList.resize(numPhase); + for(size_t i = 0; i < numPhase; i++) { + std::stringstream ss; + ss << i + 1; + phsList[i].readEBSD(hdr.openGroup(ss.str())); + } + + //open data directory + grp = grp.openGroup("Data"); + + //read phase data + phase .resize(width * height); + { + std::vector buff(width * height); + grp.openDataSet("Phase").read(buff.data(), H5::PredType::NATIVE_UINT8); + std::copy(buff.cbegin(), buff.cend(), phase.begin()); + } + + //read orientations and quality + qu .resize(width * height); + metric.resize(width * height); + imQual.resize(width * height); + { + //read orientations + Real eu[3]; + std::vector buff(width * height * 3); + float * const p0 = buff.data() ; + float * const p1 = buff.data() + width * height ; + float * const p2 = buff.data() + width * height * 2; + grp.openDataSet("Phi1").read(p0, H5::PredType::NATIVE_FLOAT); + grp.openDataSet("Phi" ).read(p1, H5::PredType::NATIVE_FLOAT); + grp.openDataSet("Phi2").read(p2, H5::PredType::NATIVE_FLOAT); + for(size_t i = 0; i < phase.size(); i++) { + eu[0] = (Real) p0[i]; + eu[1] = (Real) p1[i]; + eu[2] = (Real) p2[i]; + eu2qu(eu, qu[i].data()); + } + + //read indexing quality + grp.openDataSet("CI").read(p0, H5::PredType::NATIVE_FLOAT); + for(size_t i = 0; i < phase.size(); i++) metric[i] = (Real) p0[i]; + + //read image quality + grp.openDataSet("IQ").read(p0, H5::PredType::NATIVE_FLOAT); + for(size_t i = 0; i < phase.size(); i++) imQual[i] = (Real) p0[i]; + } + return; + + } + } + + #ifdef XTAL_USE_TSL + //first try tsl readers + if(tsl::OrientationMap::CanRead(fileName)) { + *this = OrientationMap(tsl::OrientationMap(fileName, aux)); + return; + } + #endif + + //next try hkl readers + #ifdef XTAL_USE_HKL + if(hkl::OrientationMap::CanRead(fileName)) { + *this = OrientationMap(hkl::OrientationMap(fileName)); + return; + } + #endif + + //if we haven't read the file yet no reader was found + throw std::runtime_error("couldn't find a reader for " + fileName); + } + + + //@brief : read an orientation map from a file + //@param fileName: name of file to read from + //@brief : write an orientation map to a file + //@param fileName: name of file to write to + template + void OrientationMap::write(const std::string fileName) const { + //first try to write as an h5 file + if(detail::isH5(fileName)) { + std::string folder = "Scan 1"; + H5::H5File file(fileName.c_str(), H5F_ACC_TRUNC); + writeH5(file.createGroup(folder)); + return; + } + + #ifdef XTAL_USE_TSL + //first try tsl writers + try { + toTSL().write(fileName); + return; + } catch(...) { + } + #endif + + //next try hkl writers + #ifdef XTAL_USE_HKL + try { + toHKL().write(fileName); + return; + } catch(...) { + } + #endif + + //if we haven't read the file yet no reader was found + throw std::runtime_error("couldn't find a writer for " + fileName); + } + + //@brief : write an orientation map to an hdf5 group + //@param grp: h5 group to write to + template + void OrientationMap::writeH5(H5::Group grp) const { + //make primary folders + H5::Group ebsd = grp .createGroup("EBSD" ); + H5::Group hdr = ebsd.createGroup("Header"); + H5::Group data = ebsd.createGroup("Data" ); + + //write header + float vf; + hdr.createDataSet("nColumns" , H5::PredType::NATIVE_UINT32 , H5::DataSpace(H5S_SCALAR)).write(&width , H5::PredType::NATIVE_UINT32); + hdr.createDataSet("nRows" , H5::PredType::NATIVE_UINT32 , H5::DataSpace(H5S_SCALAR)).write(&height, H5::PredType::NATIVE_UINT32); + vf = (float) xStep; + hdr.createDataSet("Step X" , H5::PredType::NATIVE_FLOAT , H5::DataSpace(H5S_SCALAR)).write(&vf , H5::PredType::NATIVE_FLOAT ); + vf = (float) yStep; + hdr.createDataSet("Step Y" , H5::PredType::NATIVE_FLOAT , H5::DataSpace(H5S_SCALAR)).write(&vf , H5::PredType::NATIVE_FLOAT ); + hdr.createDataSet("Operator" , H5::StrType(0, H5T_VARIABLE), H5::DataSpace(H5S_SCALAR)).write( owner ,H5::StrType(0, H5T_VARIABLE)); + hdr.createDataSet("Scan ID" , H5::StrType(0, H5T_VARIABLE), H5::DataSpace(H5S_SCALAR)).write( name ,H5::StrType(0, H5T_VARIABLE)); + vf = (float) calib.sTlt; + hdr.createDataSet("Sample Tilt" , H5::PredType::NATIVE_FLOAT , H5::DataSpace(H5S_SCALAR)).write(&vf , H5::PredType::NATIVE_FLOAT ); + vf = (float) calib.wd; + hdr.createDataSet("Working Distance", H5::PredType::NATIVE_FLOAT , H5::DataSpace(H5S_SCALAR)).write(&vf , H5::PredType::NATIVE_FLOAT ); + + H5::Group clb = hdr.createGroup("Pattern Center Calibration"); + vf = (float) calib.xStar; + clb.createDataSet("x-star" , H5::PredType::NATIVE_FLOAT , H5::DataSpace(H5S_SCALAR)).write(&vf , H5::PredType::NATIVE_FLOAT ); + vf = (float) calib.yStar; + clb.createDataSet("y-star" , H5::PredType::NATIVE_FLOAT , H5::DataSpace(H5S_SCALAR)).write(&vf , H5::PredType::NATIVE_FLOAT ); + vf = (float) calib.zStar; + clb.createDataSet("z-star" , H5::PredType::NATIVE_FLOAT , H5::DataSpace(H5S_SCALAR)).write(&vf , H5::PredType::NATIVE_FLOAT ); + + std::string grid("SqrGrid"); + hdr.createDataSet("Grid Type" , H5::StrType(0, H5T_VARIABLE), H5::DataSpace(H5S_SCALAR)).write( grid ,H5::StrType(0, H5T_VARIABLE)); + hdr.createDataSet("Notes" , H5::StrType(0, H5T_VARIABLE), H5::DataSpace(H5S_SCALAR)); + hdr.createDataSet("Sample ID" , H5::StrType(0, H5T_VARIABLE), H5::DataSpace(H5S_SCALAR)); + + //write phases to header + hdr = hdr.createGroup("Phase"); + // phsList.resize(numPhase); + for(size_t i = 0; i < phsList.size(); i++) { + std::stringstream ss; + ss << i + 1; + phsList[i].writeEBSD(hdr.createGroup(ss.str())); + } + + //write data + + const hsize_t num = width * height; + H5::DataSpace space(1, &num); + if(std::is_same::value) { + data.createDataSet("Phase", H5::PredType::NATIVE_UINT8, space).write(phase.data(), H5::PredType::NATIVE_UINT8); + } else { + std::vector buff(phase.cbegin(), phase.cend()); + data.createDataSet("Phase", H5::PredType::NATIVE_UINT8, space).write(buff .data(), H5::PredType::NATIVE_UINT8); + } + + //convert orientations to planar euler angles + std::vector buff(width * height * 3); + float * const p0 = buff.data() ; + float * const p1 = buff.data() + width * height ; + float * const p2 = buff.data() + width * height * 2; + Real eu[3]; + for(size_t i = 0; i < phase.size(); i++) { + qu2eu(qu[i].data(), eu); + p0[i] = (float) eu[0]; + p1[i] = (float) eu[1]; + p2[i] = (float) eu[2]; + } + + //write orientations + data.createDataSet("Phi1", H5::PredType::NATIVE_FLOAT, space).write(p0, H5::PredType::NATIVE_FLOAT); + data.createDataSet("Phi" , H5::PredType::NATIVE_FLOAT, space).write(p1, H5::PredType::NATIVE_FLOAT); + data.createDataSet("Phi2", H5::PredType::NATIVE_FLOAT, space).write(p2, H5::PredType::NATIVE_FLOAT); + + //write quality + if(std::is_same::value) { + data.createDataSet("Metric", H5::PredType::NATIVE_FLOAT, space).write(metric.data(), H5::PredType::NATIVE_FLOAT); + data.createDataSet("IQ" , H5::PredType::NATIVE_FLOAT, space).write(imQual.data(), H5::PredType::NATIVE_FLOAT); + } else { + std::transform(metric.cbegin(), metric.cend(), buff.begin(), [](const Real& v){return (float)v;}); + data.createDataSet("Metric", H5::PredType::NATIVE_FLOAT, space).write(buff .data(), H5::PredType::NATIVE_FLOAT); + std::transform(imQual.cbegin(), imQual.cend(), buff.begin(), [](const Real& v){return (float)v;}); + data.createDataSet("IQ" , H5::PredType::NATIVE_FLOAT, space).write(buff .data(), H5::PredType::NATIVE_FLOAT); + } + } + + //@brief: build an ipf color map + //@param rgb : location to write red, green, blue (, alpha) values + //@param refDir: reference direction (sample frame) + //@param h2r: hsl2rgb like coloring function to use with h, s, and l in [0,1] and output as [0,1] rgb: void(Real const * const hsl, Real * const rgb) + //@param alpha : true to write an RGBA image, false for RGB + template + void OrientationMap::ipfColor(uint8_t * const rgb, Real const * const refDir, std::function h2r, const bool alpha) const { + //normalize reference direction + const Real mag = std::sqrt(std::inner_product(refDir, refDir+3, refDir, Real(0))); + const Real n[3] = {refDir[0] / mag, refDir[1] / mag, refDir[2] / mag}; + + //build table of point groups (could just use phase[i].pg but this is more flexible for potential future optimizations) + std::vector pgs; + for(const Phase& p : phsList) pgs.push_back(p.pg); + + //loop over pixels + Real nx[3], color[4]; + color[3] = 255.0; + const size_t NUM = alpha ? 4 : 3; + for(size_t i = 0; i < qu.size(); i++) { + if(phase[i] < phsList.size()) { + qu[i].rotateVector(n, nx);//get direction in crystal frame + pgs[phase[i]].ipfColor(nx, color, h2r);//get ipf color for crystal direction + for(size_t j = 0; j < 3; j++) rgb[i * 3 + j] = (uint8_t) std::round(color[j] * 255);//[0,1] -> [0,255] + } + } + } + + //@brief: build an ipf color map + //@param refDir: reference direction (sample frame) + //@param h2r: hsl2rgb like coloring function to use with h, s, and l in [0,1] and output as [0,1] rgb: void(Real const * const hsl, Real * const rgb) + //@param alpha : true to write an RGBA image, false for RGB + //@return : red, green, blue (, alpha) values + template + std::vector OrientationMap::ipfColor(Real const * const refDir, std::function h2r, const bool alpha) const { + const std::vector rgb(qu.size() * (alpha ? 4 : 3)); + ipfColor((uint8_t * const)rgb.data(), refDir, h2r, alpha); + return rgb; + } +} + +#endif//_OrientationMap_H_ diff --git a/include/xtal/phase.hpp b/include/xtal/phase.hpp new file mode 100644 index 0000000..a52b8fa --- /dev/null +++ b/include/xtal/phase.hpp @@ -0,0 +1,139 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _phase_h_ +#define _phase_h_ + +#include "constants.hpp" + +#ifdef XTAL_USE_H5 + #include "H5Cpp.h" +#endif + +#include "symmetry.hpp" + +namespace xtal { + template + struct Phase { + static_assert(std::is_floating_point::value, "Phase must be templated on floating point type"); + + Real lat[6];//lattice constants: a,b,c,alpha,beta,gamma with a,b,c in nm and alpha,beta,gamma in degrees + std::string name ;//phase name + PointGroup pg ;//point group + + //@brief: default constructor makes an empty phase + Phase() : name("unknown"), pg(1) {std::fill(lat, lat + 6, Real(0));} + + #ifdef XTAL_USE_H5 + //@brief : read phase data from an EMsoft master pattern HDF file + //@param grp: folder in h5 file to read from (e.g. "/CrystalData") + void readMaster(H5::Group grp); + + //@brief : read phase data from an H5 ebsd file + //@param grp: folder in h5 file to read from (e.g. "/Scan Name/EBSD/Header/Phase/3") + void readEBSD(H5::Group grp); + + //@brief : write phase data to an H5 ebsd file + //@param grp: folder in h5 file to write to (e.g. "/Scan Name/EBSD/Header/Phase/3") + void writeEBSD(H5::Group grp) const; + #endif//XTAL_USE_H5 + }; +} + +//////////////////////////////////////////////////////////////////////// +// Implementations // +//////////////////////////////////////////////////////////////////////// + +namespace xtal { + +#ifdef XTAL_USE_H5 + //@brief : read phase data from an EMsoft master pattern HDF file + //@param grp: folder in h5 file to read from + template + void Phase::readMaster(H5::Group grp) { + float latParam[6]; + int32_t spaceGroup ; + try { + grp.openDataSet("LatticeParameters").read( latParam , H5::PredType::NATIVE_FLOAT); + } catch (H5::Exception&) { + //the lattice parameters dataset may not exists for merged master patterns + std::fill(latParam, latParam + 6, 0.0f);// + } + grp.openDataSet("SpaceGroupNumber" ).read(&spaceGroup, H5::PredType::NATIVE_INT32); + std::copy(latParam, latParam + 6, lat); + pg = PointGroup(spaceGroup); + } + + //@brief : read phase data from an H5 ebsd file + //@param grp: folder in h5 file to read from (e.g. "/Scan Name/EBSD/Header/Phase/3") + template + void Phase::readEBSD(H5::Group grp) { + float latParam[6]; + int32_t symNum ; + grp.openDataSet("Lattice Constant a" ).read(&latParam[0], H5::PredType::NATIVE_FLOAT); + grp.openDataSet("Lattice Constant b" ).read(&latParam[1], H5::PredType::NATIVE_FLOAT); + grp.openDataSet("Lattice Constant c" ).read(&latParam[2], H5::PredType::NATIVE_FLOAT); + grp.openDataSet("Lattice Constant alpha").read(&latParam[3], H5::PredType::NATIVE_FLOAT); + grp.openDataSet("Lattice Constant beta" ).read(&latParam[4], H5::PredType::NATIVE_FLOAT); + grp.openDataSet("Lattice Constant gamma").read(&latParam[5], H5::PredType::NATIVE_FLOAT); + grp.openDataSet("Symmetry" ).read(&symNum , H5::PredType::NATIVE_INT32); + grp.openDataSet("MaterialName" ).read( name , H5::StrType(0, H5T_VARIABLE), H5::DataSpace(H5S_SCALAR)); + for(size_t i = 0; i < 3; i++) latParam[i] /= 10;//angstrom -> nm + std::copy(latParam, latParam + 6, lat); + pg = PointGroup::FromTSL(symNum); + } + + //@brief : write phase data to an H5 ebsd file + //@param grp: folder in h5 file to write to (e.g. "/Scan Name/EBSD/Header/Phase/3") + template + void Phase::writeEBSD(H5::Group grp) const { + float latParam[6]; + for (size_t i = 0; i < 3; i++) latParam[i] = (float)lat[i] * 10;//nm -> angstrom + for (size_t i = 3; i < 6; i++) latParam[i] = (float)lat[i]; + int32_t symNum = pg.tslNum(); + hsize_t dim[1] = {1}; + H5::DataSpace dSpace(1, dim); + grp.createDataSet("Lattice Constant a" , H5::PredType::NATIVE_FLOAT, dSpace).write(&latParam[0], H5::PredType::NATIVE_FLOAT); + grp.createDataSet("Lattice Constant b" , H5::PredType::NATIVE_FLOAT, dSpace).write(&latParam[1], H5::PredType::NATIVE_FLOAT); + grp.createDataSet("Lattice Constant c" , H5::PredType::NATIVE_FLOAT, dSpace).write(&latParam[2], H5::PredType::NATIVE_FLOAT); + grp.createDataSet("Lattice Constant alpha", H5::PredType::NATIVE_FLOAT, dSpace).write(&latParam[3], H5::PredType::NATIVE_FLOAT); + grp.createDataSet("Lattice Constant beta" , H5::PredType::NATIVE_FLOAT, dSpace).write(&latParam[4], H5::PredType::NATIVE_FLOAT); + grp.createDataSet("Lattice Constant gamma", H5::PredType::NATIVE_FLOAT, dSpace).write(&latParam[5], H5::PredType::NATIVE_FLOAT); + grp.createDataSet("Symmetry" , H5::PredType::NATIVE_INT32, dSpace).write(&symNum , H5::PredType::NATIVE_INT32); + grp.createDataSet("MaterialName" , H5::StrType(0, H5T_VARIABLE), H5::DataSpace(H5S_SCALAR)).write(name, H5::StrType(0, H5T_VARIABLE), H5::DataSpace(H5S_SCALAR)); + } +#endif//XTAL_USE_H5 +} + +#endif//_phase_h_ diff --git a/include/xtal/position.hpp b/include/xtal/position.hpp new file mode 100644 index 0000000..b219312 --- /dev/null +++ b/include/xtal/position.hpp @@ -0,0 +1,1023 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _position_h_ +#define _position_h_ + +#include +#include + +namespace xtal { + //bitmasks for wyckoff position string formatting + namespace wyckoff { + enum Format { + SftX = 1,//should space be left for x translations e.g. "x ,y,z+1/2" instead of "x,y,z+1/2" + SftY = 2,//should space be left for y translations + SftZ = 4,//should space be left for z translations + SftXYZ = 7,//SftX | SftY | SftZ + Hex = 8,//should space be left for 3 fold rotations e.g. " -x,y-x,z" instead of "-x,y-x,z" + }; + } + + //@brief: helper to compactly hold a 4x4 general position matrix + struct GenPos { + + //////////////////////////////////////////////// + // methods to build a general position matrix // + //////////////////////////////////////////////// + + //@brief: construct a default general position (identity matrix) + GenPos() : GenPos(0) {} + + //@brief : construct a GenPos from a set of bits + //@param b: bits to construct from + //@note : you'd better know exactly what you're doing with the internal structure to use this + GenPos(const uint32_t b) {u.i = b;} + + //@brief: construct an identity matrix + static GenPos Identity() {return GenPos(0x0000);} + + //@brief: construct an inversion matrix + static GenPos Inversion() {return GenPos(0x0007);} + + //@brief : construct a mirror plane with the given normal + //@param n : mirror plane normal + //@param hex: true for 3/6 fold type groups, e.g. 2@100 == -x,y,z for false, -x+y,y,z for true + //@return : mirror matrix e.g. x mirror for (1,0,0) + static GenPos Mirror(int8_t const * const n, const bool hex = false); + + //@brief : construct a two fold rotation axis about the given direction + //@param n : two fold axis + //@param hex: true for 3/6 fold type groups, e.g. 2@100 == x,-y,-z for false, x-y,-y,-z for true + //@return : two fold rtation matrix + static GenPos Two(int8_t const * const n, const bool hex = false); + + //@brief : construct a three fold rotation axis about the given direction + //@param n : three fold axis (must by 001 or 111 type direction) + //@param inv: true for roto inversion (-3) + //@return : three fold rtation matrix + static GenPos Three(int8_t const * const n, const bool inv = false); + + //@brief : construct an N fold rotation about the Z axis + //@param n: rotational order (must be +/- 1,2,3,4, or 6) + //@return : rotation matrix + //@note : 1, -1 and 2 return idenity, inversion, and mirror respectively + static GenPos Z(const int8_t n); + + //////////////////////////////////////////////// + // 3x3 + augmenting vector access // + //////////////////////////////////////////////// + + //@brief : get the upper left 3x3 sub matrix + //@return: pointer to matrix in row major order + //@note : read only + int8_t const * getMat3() const {return IdxToMat3(get3x3());} + + //@brief : set the upper left 3x3 sub matrix + //@param m: matrix to use (must be one of the 64 valid 3x3 position matrices) + void setMat3(int8_t const * m); + + //@brief : get the translation in [0,1) * 24 + //@param t: location to write translation {a, b, c} + int8_t const * getTrans() const {return u.c+1;} + void getTrans(int8_t * t) const {std::copy(u.c+1, u.c+4, t);} + + //@brief : set the translation * 24 + //@param t: translation for x,y,z each in [0,1) * 24 + void setTrans(int8_t* t); + + //@brief: set translation to {0,0,0} + void removeTrans() {std::fill(u.c+1, u.c+4, 0);} + + //@brief : get the translation + //@param t: location to write translation {a, b, c} + template void getTransReal(Real * const t) const; + + //@brief : set the translation + //@param t: translation to set {a, b, c} + //@note : translations must be one of (0, 1/6, 1/4, 1/3, 1/2, 2/3, 3/4, or 5/6) +/- n*1 for normal + template void setTransReal(Real const * const t); + + //@brief : transform a hexagonal general position matrix from a 3 fold coordinate system to a cartesian frame + //@param om: location to write 3x3 matrix in cartesian frame + //@note : throws for cubic matricies + template void getMat3HexCart(Real * const om); + + //////////////////////////////////////////////// + // property queries // + //////////////////////////////////////////////// + + //@brief : get the determinant of the 3x3 matrix + //@return: determinant + int_fast8_t det() const; + + //@brief : get the trace of the 3x3 matrix + //@return: trace + int_fast8_t tr() const; + + //@brief : get the rotational order of the 3x3 matrix + //@return: n * det() such that matrix^n == I, must be +/- 1, 2, 3, 4, 6 + //@note : e.g. 1 for identity, -2 for mirror plane, 3 for 3 fold rotation etc + int_fast8_t order() const; + + //@brief : get the axis associated with the 3x3 matrix (i.e. rotation axis or mirror plane normal) + //@return: rotation axis as e.g. {2,1,0} + //@note : identity and inversion return {1,0,0} + int8_t const* axis() const; + + //@brief : determine if this matrix contains a translation component + //@return: true if there is a translation, false if the translation is {0,0,0} + //@note : in conjunction with order this enables all possible element types to be distinguished + // switch(order()) { + // case 1 : hasTranslation() ? "translation" : "identity" + // case 2,3,4,6: hasTranslation() ? "screw" : "rotation" + // case -1 : hasTranslation() ? "" : "inversion" + // case -2 : hasTranslation() ? "glide" : "mirror" + // case -3,-4,-6: hasTranslation() ? "" : "rotoinversion" + // } + bool hasTranslation() const {return 0 != getTrans()[0] || 0 != getTrans()[1] || 0 != getTrans()[2];} + + //@brief : check if a general position is identity + //@return: true/false if this does/doesn't represent identity + bool isIdent() const {return *this == Identity();} + + //////////////////////////////////////////////// + // matrix operations // + //////////////////////////////////////////////// + + //@brief : multiply this general position with another + //@param rhs: other general position to multiply with + //@return : this + GenPos& operator*=(const GenPos& rhs); + + //@brief : multiply two position with another + //@param rhs: other general position to multiply with + //@return : GenPos(this * rhs) + GenPos operator*(const GenPos& rhs) const {return GenPos(*this) *= rhs;} + + //@brief : get the inverse of this general position + //@return : this^-1 + GenPos inverse() const; + + //@brief : apply a transformation matrix to this general position + //@param p: transformation matrix + //@note : applys basis transform A' = P^-1 * A * P for P == I | p + GenPos transform(const GenPos& p) const {return p.inverse().operator*=(*this) * p;} + + //@brief : shift the origin by a given vector + //@param p: origin translation for x,y,z each in [0,1) * 24 + //@note : this applys a special case of basis transform A' = P^-1 * A * P for P == I | -p + GenPos shiftOrigin(int8_t const*const p) const; + + //@brief : since general positions can be described by a single int comparison is easy + //@param rhs: other general position to compare against + //@return : this < rhs + bool operator<(const GenPos& rhs) const {return u.i < rhs.u.i;} + + //@brief : since general positions can be described by a single int comparison is easy + //@param rhs: other general position to compare against + //@return : this == rhs + bool operator==(const GenPos& rhs) const {return u.i == rhs.u.i;} + + //@brief : since general positions can be described by a single int comparison is easy + //@param rhs: other general position to compare against + //@return : this == rhs + bool operator!=(const GenPos& rhs) const {return u.i != rhs.u.i;} + + //@brief : build closed set of general positions + //@param gen: set of general positions to close + //@return : closed set of general positions + static std::vector CloseSet(std::vector gen); + + //////////////////////////////////////////////// + // IO // + //////////////////////////////////////////////// + + //@brief : convert to string representation + //@param pre: prefix for each line of matrix + //@return : string representation of 4x3 general position matrix + std::string to_string(std::string pre = "") const; + + //@brief : convert to string representation + //@param fmt: bitmask of format options + //@return : string representation of 4x3 general position matrix + std::string to_wyckoff(const int fmt = wyckoff::SftX | wyckoff::SftY | wyckoff::SftZ | wyckoff::Hex) const; + + //@brief : build general position matrix from a piece of an EMsoft generator string + //@param str: 4 character EMsoft generator string e.g. "eBFF" + void fromEMsoft(char const * str); + + //@brief : convert stored position matrix to EMsoft generator string if possible + //@param str: location to write 4 character EMsoft generator string e.g. "eBFF" + void toEMsoft(char * str, const bool ori = false) const; + + //////////////////////////////////////////////// + // detail (exposed primariily for testing) // + //////////////////////////////////////////////// + + //@brief : get a 3x3 position matrix from an index + //@param i: index of matrix to get, must be [0,64) + //@return : pointer to 3x3 matrix corresponding to i (row major) + static int8_t const * IdxToMat3(const int_fast8_t i); + + //@brief : get the index of a 3x3 position matrix + //@param m: 3x3 matrix to get index of + //@return : index of matrix m (such that IdxToMat3(index) has the same matrix as m) + //@note : returns 0xFF if m is invalid (not in 64 possible returns of IdxToMat3) + //@note : entries of m should be exclusively -1, 0, or 1 + static uint_fast8_t Mat3ToIdx(int8_t const * const m); + + private: + //a general position is a augmented 3x3 matrix + //there are only 64 crystallographically valid 3x3 matrix types + //there are only 12 crystallographically valid translations (all 24ths) + //that means we need at least log(64 * 12^3, 2) == 17 bits to store any possible matrix + union { + uint32_t i ; + int8_t c[4];//index of matrix + 3x translation * 24 - matrix must be [0,64), translations should be [0,24) {really [0,21]} + } u; + + //@brief : get the 6 bits representing the 3x3 matrix + //@return: 3x3 matrix index [0,64) + int_fast8_t get3x3() const {return u.c[0];} + + //@brief : set the 6 bits representing the 3x3 matrix + //@param i: 3x3 matrix index [0,64) + void set3x3(const int_fast8_t i) {u.c[0] = i;} + }; +} + +//////////////////////////////////////////////////////////////////////// +// Implementations // +//////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include + +namespace xtal { + + //////////////////////////////////////////////////////////////////////// + // Helper Functions // + //////////////////////////////////////////////////////////////////////// + + namespace detail { + + //@brief : get string representation of a 24th fraction + //@param num: numerator (must be in [0,24]) + //@param uni: should unicode characters be used to make nicer fractions + //@return : string representation of num/24 + std::string get24th(const size_t num, const bool uni) { + static const bool vul = false;//if unicode characters are used should vulgar fractions be used where possible + switch(num) { + case 0: return uni ? ( vul ? "0" : " 0 " ) : " 0 " ; + case 1: return uni ? ( "¹⧸₂₄") : "1/24" ; + case 2: return uni ? ( "¹⧸₁₂") : "1/12" ; + case 3: return uni ? ( vul ? "⅛" : "¹⧸₈" ) : "1/8" ; + case 4: return uni ? ( vul ? "⅙" : "¹⧸₆" ) : "1/6" ; + case 5: return uni ? ( "⁵⧸₂₄") : "5/24" ; + case 6: return uni ? ( vul ? "¼" : "¹⧸₄" ) : "1/4" ; + case 7: return uni ? ( "⁷⧸₂₄") : "7/24" ; + case 8: return uni ? ( vul ? "⅓" : "¹⧸₃" ) : "1/3" ; + case 9: return uni ? ( vul ? "⅜" : "³⧸₈" ) : "3/8" ; + case 10: return uni ? ( "⁵⧸₁₂") : "5/12" ; + case 11: return uni ? ( "¹¹⧸₂₄") : "11/24"; + case 12: return uni ? ( vul ? "½" : "¹⧸₂" ) : "1/2" ; + case 13: return uni ? ( "¹³⧸₂₄") : "13/24"; + case 14: return uni ? ( "⁷⧸₁₂") : "7/12" ; + case 15: return uni ? ( vul ? "⅝" : "⁵⧸₈" ) : "5/8" ; + case 16: return uni ? ( vul ? "⅔" : "²⧸₃" ) : "2/3" ; + case 17: return uni ? ( "¹⁷⧸₂₄") : "17/24"; + case 18: return uni ? ( vul ? "¾" : "³⧸₄" ) : "3/4" ; + case 19: return uni ? ( "¹⁹⧸₂₄") : "19/24"; + case 20: return uni ? ( vul ? "⅚" : "⁵⧸₆" ) : "5/6" ; + case 21: return uni ? ( vul ? "⅞" : "⁷⧸₈" ) : "7/8" ; + case 22: return uni ? ( "¹¹⧸₁₂") : "11/12"; + case 23: return uni ? ( "²³⧸₂₄") : "23/24"; + default: throw std::runtime_error("get24th argument must lie in [0,24)"); + } + } + } + + //////////////////////////////////////////////////////////////////////// + // General Position Members // + //////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////// + // methods to build a general position matrix // + //////////////////////////////////////////////// + + //@brief : construct a mirror plane with the given normal + //@param n : mirror plane normal + //@param hex: true for 3/6 fold type groups, e.g. 2@100 == -x,y,z for false, -x+y,y,z for true + //@return : mirror matrix e.g. x mirror for (1,0,0) + GenPos GenPos::Mirror(int8_t const * const n, const bool hex) { + if ( ( 1 == n[0] && 0 == n[1] && 0 == n[2]) || (-1 == n[0] && 0 == n[1] && 0 == n[2]) ) return GenPos(hex ? 0x0036 : 0x0001);//x + else if( ( 0 == n[0] && 1 == n[1] && 0 == n[2]) || ( 0 == n[0] && -1 == n[1] && 0 == n[2]) ) return GenPos(hex ? 0x0038 : 0x0002);//y + else if( ( 0 == n[0] && 0 == n[1] && 1 == n[2]) || ( 0 == n[0] && 0 == n[1] && -1 == n[2]) ) return GenPos(0x0004);//z (hex and cubic) + else if( ( 0 == n[0] && 1 == n[1] && -1 == n[2]) || ( 0 == n[0] && -1 == n[1] && 1 == n[2]) ) return GenPos(0x0018);//y-z + else if( ( 0 == n[0] && 1 == n[1] && 1 == n[2]) || ( 0 == n[0] && -1 == n[1] && -1 == n[2]) ) return GenPos(0x001E);//y+z + else if( ( 1 == n[0] && -1 == n[1] && 0 == n[2]) || (-1 == n[0] && 1 == n[1] && 0 == n[2]) ) return GenPos(0x0020);//x-y (hex and cubic) + else if( ( 1 == n[0] && 1 == n[1] && 0 == n[2]) || (-1 == n[0] && -1 == n[1] && 0 == n[2]) ) return GenPos(0x0023);//x+y (hex and cubic) + else if( (-1 == n[0] && 0 == n[1] && 1 == n[2]) || ( 1 == n[0] && 0 == n[1] && -1 == n[2]) ) return GenPos(0x0028);//x-z + else if( ( 1 == n[0] && 0 == n[1] && 1 == n[2]) || (-1 == n[0] && 0 == n[1] && -1 == n[2]) ) return GenPos(0x002D);//x+z + else if( ( 1 == n[0] && 2 == n[1] && 0 == n[2]) || (-1 == n[0] && -2 == n[1] && 0 == n[2]) ) return GenPos(0x0034);//(hex only) + else if( ( 2 == n[0] && 1 == n[1] && 0 == n[2]) || (-2 == n[0] && -1 == n[1] && 0 == n[2]) ) return GenPos(0x003A);//(hex only) + else throw std::runtime_error("unsupported mirror"); + } + + //@brief : construct a two fold rotation axis about the given direction + //@param n : two fold axis + //@param hex: true for 3/6 fold type groups, e.g. 2@100 == x,-y,-z for false, x-y,-y,-z for true + //@return : two fold rtation matrix + GenPos GenPos::Two(int8_t const * const n, const bool hex) { + if ( ( 1 == n[0] && 0 == n[1] && 0 == n[2]) || (-1 == n[0] && 0 == n[1] && 0 == n[2]) ) return GenPos(hex ? 0x0035 : 0x0006);//x + else if( ( 0 == n[0] && 1 == n[1] && 0 == n[2]) || ( 0 == n[0] && -1 == n[1] && 0 == n[2]) ) return GenPos(hex ? 0x003B : 0x0005);//y + else if( ( 0 == n[0] && 0 == n[1] && 1 == n[2]) || ( 0 == n[0] && 0 == n[1] && -1 == n[2]) ) return GenPos(0x0003);//z (hex and cubic) + else if( ( 0 == n[0] && 1 == n[1] && -1 == n[2]) || ( 0 == n[0] && -1 == n[1] && 1 == n[2]) ) return GenPos(0x001F);//y-z + else if( ( 0 == n[0] && 1 == n[1] && 1 == n[2]) || ( 0 == n[0] && -1 == n[1] && -1 == n[2]) ) return GenPos(0x0019);//y+z + else if( ( 1 == n[0] && -1 == n[1] && 0 == n[2]) || (-1 == n[0] && 1 == n[1] && 0 == n[2]) ) return GenPos(0x0027);//x-y (hex and cubic) + else if( ( 1 == n[0] && 1 == n[1] && 0 == n[2]) || (-1 == n[0] && -1 == n[1] && 0 == n[2]) ) return GenPos(0x0024);//x+y (hex and cubic) + else if( (-1 == n[0] && 0 == n[1] && 1 == n[2]) || ( 1 == n[0] && 0 == n[1] && -1 == n[2]) ) return GenPos(0x002F);//x-z + else if( ( 1 == n[0] && 0 == n[1] && 1 == n[2]) || (-1 == n[0] && 0 == n[1] && -1 == n[2]) ) return GenPos(0x002A);//x+z + else if( ( 1 == n[0] && 2 == n[1] && 0 == n[2]) || (-1 == n[0] && -2 == n[1] && 0 == n[2]) ) return GenPos(0x0037);//(hex only) + else if( ( 2 == n[0] && 1 == n[1] && 0 == n[2]) || (-2 == n[0] && -1 == n[1] && 0 == n[2]) ) return GenPos(0x0039);//(hex only) + else throw std::runtime_error("unsupported two"); + } + + //@brief : construct a three fold rotation axis about the given direction + //@param n : three fold axis (must by 001 or 111 type direction) + //@param inv: true for roto inversion (-3) + //@return : three fold rtation matrix + GenPos GenPos::Three(int8_t const * const n, const bool inv) { + if(0 == n[0] && 0 == n[1] && 1 == std::abs(n[2])) return Z(inv ? -3 : 3);//handle z specially + if ( 1 == n[0] && 1 == n[1] && 1 == n[2] ) return GenPos(inv ? 0x000F : 0x0008);// 1 1 1 + else if(-1 == n[0] && 1 == n[1] && 1 == n[2] ) return GenPos(inv ? 0x0012 : 0x0015);//-1 1 1 + else if( 1 == n[0] && -1 == n[1] && 1 == n[2] ) return GenPos(inv ? 0x0014 : 0x0013);// 1-1 1 + else if( 1 == n[0] && 1 == n[1] && -1 == n[2] ) return GenPos(inv ? 0x0011 : 0x0016);// 1 1-1 + else if(-1 == n[0] && -1 == n[1] && -1 == n[2] ) return GenPos(inv ? 0x0017 : 0x0010);//-1-1-1 + else if( 1 == n[0] && -1 == n[1] && -1 == n[2] ) return GenPos(inv ? 0x000C : 0x000B);// 1-1-1 + else if(-1 == n[0] && 1 == n[1] && -1 == n[2] ) return GenPos(inv ? 0x0009 : 0x000E);//-1 1-1 + else if(-1 == n[0] && -1 == n[1] && 1 == n[2] ) return GenPos(inv ? 0x000A : 0x000D);//-1-1 1 + else throw std::runtime_error("unsupported three"); + } + + //@brief : construct an N fold rotation about the Z axis + //@param n: rotational order (must be +/- 1,2,3,4, or 6) + //@return : rotation matrix + //@note : 1, -1 and 2 return idenity, inversion, and mirror respectively + GenPos GenPos::Z(const int8_t n) { + switch(n) { + case 1: return Identity(); + case 2: return GenPos(0x0003); + case 3: return GenPos(0x003C); + case 4: return GenPos(0x0021); + case 6: return GenPos(0x0030); + case -1: return Inversion(); + case -2: return GenPos(0x0004); + case -3: return GenPos(0x003F); + case -4: return GenPos(0x0026); + case -6: return GenPos(0x0033); + default: throw std::runtime_error("unsupported Z"); + } + } + + //////////////////////////////////////////////// + // 3x3 + augmenting vector access // + //////////////////////////////////////////////// + + //@brief : set the upper left 3x3 sub matrix + //@param m: matrix to use (must be one of the 64 valid 3x3 position matrices) + void GenPos::setMat3(int8_t const * m) { + uint_fast8_t i = Mat3ToIdx(m); + if(0xFF == i) throw std::runtime_error("matrix isn't one of 64 valid 3x3 general positions"); + set3x3(i); + } + + //@brief : set the translation * 24 + //@param t: translation for x,y,z each in [0,1) * 24 + void GenPos::setTrans(int8_t* t) { + //copy translations + u.c[1] = t[0]; + u.c[2] = t[1]; + u.c[3] = t[2]; + + //bring to [0,1) + for(size_t i = 0; i < 3; i++) { + while(u.c[i+1] < 0) u.c[i+1] += 24; + u.c[i+1] = u.c[i+1] % 24; + } + } + + //@brief : get the translation + //@param t: location to write translation {a, b, c} + template void GenPos::getTransReal(Real * const t) const { + std::transform(getTrans(), getTrans()+3, t, [](const int v){return Real(v) / 24;}); + } + + //@brief : set the translation + //@param t: translation to set {a, b, c} + //@note : translations must be one of (0, 1/6, 1/4, 1/3, 1/2, 2/3, 3/4, or 5/6) +/- n*1 for normal + template void GenPos::setTransReal(Real const * const t) { + int8_t iTrans[3];//integer translation + for(size_t i = 0; i < 3; i++) { + Real v = t[i] * 24;//multiply by 24 + int vi = (int)std::round(v);//convert to nearest fraction + if(std::fabs(v - vi) > std::numeric_limits::epsilon() * 10) throw std::runtime_error("translation too far from fraction of 24"); + while(vi < 0) vi += 24; + while(vi > 23) vi -= 24; + iTrans[i] = (int8_t)vi;//save integer version - cast is safe since we brought vi to [0,24) + } + setTrans(iTrans); + } + + //@brief : transform a hexagonal general position matrix from a 3 fold coordinate system to a cartesian frame + //@param om: location to write 3x3 matrix in cartesian frame + //@note : throws for cubic matricies + template void GenPos::getMat3HexCart(Real * const om) { + //compute A * m * A^-1 where A = {1, -0.5, 0}, {0, sqrt(3)/2, 0}, {0, 0, 1} ==> A^-1 = {1, 1/sqrt(3), 0}, {0, 2/sqrt(3), 0}, {0, 0, 1} + //this is a simplification assuming m[2,5,6,7] == 0 + int8_t const * m = getMat3(); + if(!(0 == m[2] && 0 == m[5] && 0 == m[6] && 0 == m[7])) throw std::runtime_error("getMat3HexCart requires hexagonal type matrix"); + static const Real k32 = std::sqrt(Real(3))/2; + om[0] = Real(m[0] * 2 - m[3]) / 2; + om[1] =(Real(m[0] * 2 - m[3] + m[1] * 4 - m[4] * 2) / 3) * k32; + om[3] = Real(m[3]) * k32; + om[4] = Real(m[3] + m[4] * 2) / 2; + om[2] = om[5] = om[6] = om[7] = 0; + om[8] = m[8]; + } + + //////////////////////////////////////////////// + // property queries // + //////////////////////////////////////////////// + + //@brief : get the determinant of the 3x3 matrix + //@return: determinant + int_fast8_t GenPos::det() const { + int8_t const* m = getMat3(); + return (m[0] * m[4] * m[8] + m[1] * m[5] * m[6] + m[2] * m[3] * m[7]) - + (m[0] * m[5] * m[7] + m[1] * m[3] * m[8] + m[2] * m[4] * m[6]); + } + + //@brief : get the trace of the 3x3 matrix + //@return: trace + int_fast8_t GenPos::tr() const { + int8_t const* m = getMat3(); + return m[0] + m[4] + m[8]; + } + + //@brief : get the type of the 3x3 matrix + //@return: type + //@note : type is n * det() such that matrix^n == I and must be +/- 1, 2, 3, 4, 6 + // e.g. 1 for identity, -2 for mirror plane, 3 for 3 fold rotation etc + int_fast8_t GenPos::order() const { + //get matrix and trace/determinant + int8_t const* m = getMat3(); + int_fast8_t d = (m[0] * m[4] * m[8] + m[1] * m[5] * m[6] + m[2] * m[3] * m[7]) + - (m[0] * m[5] * m[7] + m[1] * m[3] * m[8] + m[2] * m[4] * m[6]); + int_fast8_t t = m[0] + m[4] + m[8]; + + //get type from both (international tables table 11.2.1.1) + if(1 == d) { + switch(t) { + case -1: return 2; + case 0: return 3; + case 1: return 4; + case 2: return 6; + case 3: return 1; + default: throw std::logic_error("unexpected trace for det == +1"); + } + } else if(-1 == d) { + switch(t) { + case -3: return -1; + case -2: return -6; + case -1: return -4; + case 0: return -3; + case 1: return -2; + default: throw std::logic_error("unexpected trace for det == -1"); + } + } else { + throw std::logic_error("non +/-1 determinant"); + } + return 0; + } + + //@brief : get the axis associated with the 3x3 matrix (i.e. rotation axis or mirror plane normal) + //@return: rotation axis as e.g. {2,1,0} + //@note : identity and inversion return {1,0,0} + int8_t const* GenPos::axis() const { + //get matrix and principal eigenvector + int8_t const* m = getMat3();//get matrix we need principal eigenvector of + const int8_t d = det();//determine principal eigenvalue: +/-1 + + //build A - I * y (all elements are 0, +/-1, +/-2) + int_fast8_t a[9] = { + (int_fast8_t)(m[0] - d), (int_fast8_t)(m[1] ), (int_fast8_t)(m[2] ), + (int_fast8_t)(m[3] ), (int_fast8_t)(m[4] - d), (int_fast8_t)(m[5] ), + (int_fast8_t)(m[6] ), (int_fast8_t)(m[7] ), (int_fast8_t)(m[8] - d), + }; + + //there actually extremely limited options for eigen vectors + //the easiest solution is just to check all of them + static const int8_t v[15][3] = { + { 1, 0, 0}, { 0, 1, 0}, { 0, 0, 1}, { 0, 1, 1}, { 1, 0, 1}, + { 1, 1, 0}, { 0,-1, 1}, {-1, 0, 1}, { 1,-1, 0}, { 1, 1, 1}, + { 1,-1, 1}, {-1,-1, 1}, {-1, 1, 1}, { 2, 1, 0}, { 1, 2, 0}, + }; + + //loop over possible eigen vectors searching for a match + for(size_t i = 0; i < 15; i++) { + if(0 != v[i][0] * a[0] + v[i][1] * a[1] + v[i][2] * a[2]) continue;//does v * the first row == 0 + if(0 != v[i][0] * a[3] + v[i][1] * a[4] + v[i][2] * a[5]) continue;//does v * the second row == 0 + if(0 != v[i][0] * a[6] + v[i][1] * a[7] + v[i][2] * a[8]) continue;//does v * the third row == 0 + return v[i];//if we made it this far (A-I*y)*v == 0 + } + throw std::logic_error("no suitable eigenvector found");//we're in trouble if we didn't find a match + } + + //////////////////////////////////////////////// + // matrix operations // + //////////////////////////////////////////////// + + //@brief : multiply this general position with another + //@param rhs: other general position to multiply with + //@return : this + GenPos& GenPos::operator*=(const GenPos& rhs) { + //first extract both 3x3 matrices and the translations + int8_t const * a = getMat3 (); + int8_t const * b = rhs.getMat3 (); + int8_t const * ta = getTrans(); + int8_t const * tb = rhs.getTrans(); + + //do the 3x3 multiplication (c = a * b) + int8_t c[9] = { + (int8_t)(a[0] * b[0] + a[1] * b[3] + a[2] * b[6]), (int8_t)(a[0] * b[1] + a[1] * b[4] + a[2] * b[7]), (int8_t)(a[0] * b[2] + a[1] * b[5] + a[2] * b[8]), + (int8_t)(a[3] * b[0] + a[4] * b[3] + a[5] * b[6]), (int8_t)(a[3] * b[1] + a[4] * b[4] + a[5] * b[7]), (int8_t)(a[3] * b[2] + a[4] * b[5] + a[5] * b[8]), + (int8_t)(a[6] * b[0] + a[7] * b[3] + a[8] * b[6]), (int8_t)(a[6] * b[1] + a[7] * b[4] + a[8] * b[7]), (int8_t)(a[6] * b[2] + a[7] * b[5] + a[8] * b[8]), + }; + + //compute the new translation + int8_t t[3] = { + (int8_t)(ta[0] + tb[0] * a[0] + tb[1] * a[1] + tb[2] * a[2]), + (int8_t)(ta[1] + tb[0] * a[3] + tb[1] * a[4] + tb[2] * a[5]), + (int8_t)(ta[2] + tb[0] * a[6] + tb[1] * a[7] + tb[2] * a[8]), + }; + + //bring translation to [0,1) + for(size_t i = 0; i < 3; i++) { + while(t[i] < 0) t[i] += 24; + while(t[i] > 23) t[i] -= 24; + } + + //save results and return + setMat3(c); + setTrans(t); + return *this; + } + + //@brief : get the inverse of this general position + //@return : this^-1 + GenPos GenPos::inverse() const { + //lookup table for inverse of 3x3 component + static const int8_t lut[64] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x10, 0x14, 0x11, 0x15, 0x12, 0x16, 0x13, 0x17, + 0x08, 0x0A, 0x0C, 0x0E, 0x09, 0x0B, 0x0D, 0x0F, 0x18, 0x19, 0x1C, 0x1D, 0x1A, 0x1B, 0x1E, 0x1F, + 0x20, 0x22, 0x21, 0x23, 0x24, 0x26, 0x25, 0x27, 0x28, 0x2C, 0x2A, 0x2E, 0x29, 0x2D, 0x2B, 0x2F, + 0x3E, 0x3F, 0x3C, 0x3D, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x32, 0x33, 0x30, 0x31, + }; + + //create matrix from inverse of 3x3 part and get 3x3 part + GenPos p((uint16_t)lut[get3x3()]); + int8_t const* mInv = p.getMat3(); + + //extra transformation and compute mInv * t + int8_t const * t = getTrans(); + int8_t tNew[3] = { + (int8_t) -(mInv[0] * t[0] + mInv[1] * t[1] + mInv[2] * t[2]), + (int8_t) -(mInv[3] * t[0] + mInv[4] * t[1] + mInv[5] * t[2]), + (int8_t) -(mInv[6] * t[0] + mInv[7] * t[1] + mInv[8] * t[2]), + }; + + //bring new translation back to [0,1) and save + for(size_t i = 0; i < 3; i++) { + while(tNew[i] < 0) tNew[i] += 24; + tNew[i] = tNew[i] % 24; + } + p.setTrans(tNew); + return p; + } + + //@brief : shift the origin by a given vector + //@param p: origin translation for x,y,z each in [0,1) * 24 + //@note : this applys a special case of basis transform A' = P^-1 * A * P for P == I | p + GenPos GenPos::shiftOrigin(int8_t const*const p) const { + //get the matrix and translation + int8_t const * W = getMat3 (); + int8_t const * w = getTrans(); + + //now compute new translation as w' = w + (W - I) * p + int8_t wp[3] = { + int8_t(w[0] + (W[0] - 1) * p[0] + W[1] * p[1] + W[2] * p[2]), + int8_t(w[1] + W[3] * p[0] + (W[4] - 1) * p[1] + W[5] * p[2]), + int8_t(w[2] + W[6] * p[0] + W[7] * p[1] + (W[8] - 1) * p[2]), + }; + + //bring w' back to [0,24) and save + for(size_t i = 0; i < 3; i++) { + while(wp[i] < 0) wp[i] += 24; + while(wp[i] > 23) wp[i] -= 24; + }; + GenPos pos(*this); + pos.setTrans(wp); + return pos; + } + + //@brief : build closed set of general positions + //@param gen: set of general positions to close + //@return : closed set of general positions + std::vector GenPos::CloseSet(std::vector gen) { + if(gen.empty()) return gen;//handle empty set + + //initialize closed set with identity, gen, and gen^2 + std::set s; + s.insert(GenPos::Identity()); + for(const GenPos& p : gen) s.insert(p ); + for(const GenPos& p : gen) s.insert(p*p); + + //now repeatedly multiply until set is closed + size_t num = 0; + while(s.size() > num) {//are there new positions since the last loop? + num = s.size();//how many positions did we start with + for(const GenPos& a : s) { + for(const GenPos& b : s) { + s.insert(a * b);//add all combination of existing general positions + } + } + } + return std::vector(s.cbegin(), s.cend()); + } + + //////////////////////////////////////////////// + // IO // + //////////////////////////////////////////////// + + //@brief : convert to string representation + //@param pre: prefix for each line of matrix + //@return : string representation of 4x3 general position matrix + std::string GenPos::to_string(std::string pre) const { + //get 3x3 matrix and translation + int8_t const * m = getMat3 (); + int8_t const * t = getTrans(); + + //now write out string + std::stringstream ss; + const bool uni = true; + for(size_t j = 0; j < 3; j++) { + ss << pre; + for(size_t i = 0; i < 3; i++) ss << std::setw(3) << (int)m[3*j+i]; + ss << " | " << detail::get24th(t[j], uni) << '\n'; + } + return ss.str(); + } + + //@brief : convert to string representation + //@param fmt: bitmask of format options + //@return : string representation of 4x3 general position matrix + std::string GenPos::to_wyckoff(const int fmt) const { + static const bool uni = true;//should unicode be used to make nicer \bar{x} + static const std::string fracPad = " ";//only 2 spaces for vulgar fractions + static const std::string xyz[3] = { uni ? "x" : " x", uni ? "y" : " y", uni ? "z" : " z"};//extra space without unicode to stay aligned with \bar{x} -> -x + static const std::string negXYZ[3] = { "-x", "-y", "-z"}; + static const std::string posXYZ[3] = { "+x", "+y", "+z"}; + static const std::string barXYZ[3] = { uni ? "x̅" : "-x", uni ? "y̅" : "-y", uni ? "z̅" : "-z"};// overline is \u0305 so \bar{x} is "x\u0305" + const bool hex = fmt & wyckoff::Hex; + const bool sft[3] = { + (fmt & wyckoff::SftX) ? true : false, + (fmt & wyckoff::SftY) ? true : false, + (fmt & wyckoff::SftZ) ? true : false, + }; + + //get matrix + translation + int8_t const * const m = getMat3 (); + int8_t const * const t = getTrans(); + + std::stringstream ss; + for(size_t j = 0; j < 3; j++) { + //print out letters and count number printed + size_t count = 0; + for(size_t i = 0; i < 3; i++) { + switch(m[j*3+i]) { + case -1: ss << ((count++ > 0) ? negXYZ[i] : barXYZ[i]); break; + case 1: ss << ((count++ > 0) ? posXYZ[i] : xyz[i]); break; + default: break; + } + } + + //pad for hexagonal type matrices + if(hex && j < 2) {//there is only 3 fold about z + switch(count) { + case 0: //intentional fall through + case 3: throw std::logic_error("there should be 1 or 2 non-zero entries per matrix row in wyckoff conversion"); + case 1: ss << " "; break;//to align with e.g. x-y + case 2: break; + } + } + + //now add fraction if needed + if(0 == t[j]) { + if(sft[j]) ss << fracPad; + } else { + ss << "+" << detail::get24th(t[j], uni); + } + + //add separator between rows + if(j < 2) ss << ','; + } + return ss.str(); + } + + //@brief : convert an EMsoft generator string to a general position matrix + //@param str: 4 character EMsoft generator string e.g. "eBFF" + //@return : 16 bit encoded general position matrix + void GenPos::fromEMsoft(char const * str) { + //parse 3x3 generator matrix + int8_t m[9] = {0,0,0, 0,0,0, 0,0,0}; + switch(str[0]) { + case '1': //intentional fall through (alternate origin) + case 'a': m[0] = 1; m[4] = 1; m[8] = 1; break;// 0x00: identity + case 'b': m[0] = -1; m[4] = -1; m[8] = 1; break;// 0x03: {-1, 0, 0, 0,-1, 0, 0, 0, 1} 2@z + case 'c': m[0] = -1; m[4] = 1; m[8] = -1; break;// 0x05: {-1, 0, 0, 0, 1, 0, 0, 0,-1} 2@y + case 'd': m[2] = 1; m[3] = 1; m[7] = 1; break;// 0x08: { 0, 0, 1, 1, 0, 0, 0, 1, 0} 3@111 + case 'e': m[1] = 1; m[3] = 1; m[8] = -1; break;// 0x24: { 0, 1, 0, 1, 0, 0, 0, 0,-1} 2@110 + case 'f': m[1] = -1; m[3] = -1; m[8] = -1; break;// 0x27: { 0,-1, 0, -1, 0, 0, 0, 0,-1} 2@1-10 + case 'g': m[1] = -1; m[3] = 1; m[8] = 1; break;// 0x21: { 0,-1, 0, 1, 0, 0, 0, 0, 1} 4@z + case 'h': m[0] = -1; m[4] = -1; m[8] = -1; break;// 0x07: {-1, 0, 0, 0,-1, 0, 0, 0,-1} inversion + case 'i': m[0] = 1; m[4] = 1; m[8] = -1; break;// 0x04: { 1, 0, 0, 0, 1, 0, 0, 0,-1} mz + case 'j': m[0] = 1; m[4] = -1; m[8] = 1; break;// 0x02: { 1, 0, 0, 0,-1, 0, 0, 0, 1} my + case 'k': m[1] = -1; m[3] = -1; m[8] = 1; break;// 0x23: { 0,-1, 0, -1, 0, 0, 0, 0, 1} m110 + case 'l': m[1] = 1; m[3] = 1; m[8] = 1; break;// 0x20: { 0, 1, 0, 1, 0, 0, 0, 0, 1} m1-10 + case 'm': m[1] = 1; m[3] = -1; m[8] = -1; break;// 0x26: { 0, 1, 0, -1, 0, 0, 0, 0,-1} -4@z + case 'n': m[1] = -1; m[3] = 1; m[4] = -1; m[8] = 1; break;// 0x3C: { 0,-1, 0, 1,-1, 0, 0, 0, 1} 3@z + + //additions for monoclinic a + case 'o': m[0] = 1; m[4] = -1; m[8] = -1; break;// 0x06: { 1, 0, 0, 0,-1, 0, 0, 0,-1} 2@x + case 'p': m[0] = -1; m[4] = 1; m[8] = 1; break;// 0x01: {-1, 0, 0, 0, 1, 0, 0, 0, 1} mx + + default: throw std::runtime_error("failed to parse 3x3 matrix from EMsoft generator string `" + std::string(str, str + 4) + "'"); + } + + //parse the translation + int8_t t[3]; + for(size_t i = 0; i < 3; i++) {//loop over x, y, z + switch(str[1+i]) { + case 'O': t[i] = 0; break;//no translation + case 'A': t[i] = 4; break;// 1/6 + case 'B': t[i] = 6; break;// 1/4 + case 'C': t[i] = 8; break;// 1/3 + case 'D': t[i] = 12; break;// 1/2 + case 'E': t[i] = 16; break;// 2/3 + case 'F': t[i] = 18; break;// 3/4 + case 'G': t[i] = 20; break;// 5/6 + case 'X': t[i] = -9; break;// -3/8 + case 'Y': t[i] = -6; break;// -1/4 + case 'Z': t[i] = -3; break;// -1/8 + default: throw std::runtime_error("failed to parse translation from EMsoft generator string `" + std::string(str, str + 4) + "'"); + } + } + + //save result + setMat3(m); + setTrans(t); + } + + //@brief : convert a general position matrix to an EMsoft generator string + //@param gen: 16 bit encoded general position matrix + //@param str: location to write 4 character EMsoft generator string e.g. "eBFF" + void GenPos::toEMsoft(char * str, const bool ori) const { + int8_t const * t = getTrans(); + + if(ori) { + //write 3x3 character + if(1 != order()) throw std::runtime_error("origin shift has non-identity matrix"); + str[0] = '1'; + + //write translation characters + for(size_t i = 0; i < 3; i++) { + switch(t[i]) { + case 0: str[i+1] = 'O'; break;//no translation + case 15: str[i+1] = 'X'; break;// -3/8 + case 18: str[i+1] = 'Y'; break;// -1/4 + case 21: str[i+1] = 'Z'; break;// -1/8 + default: throw std::runtime_error("couldn't map translation to EMsoft character"); + } + } + + } else { + //write 3x3 character + switch(get3x3()) { + case 0x00: str[0] = 'a'; break; + case 0x03: str[0] = 'b'; break; + case 0x05: str[0] = 'c'; break; + case 0x08: str[0] = 'd'; break; + case 0x24: str[0] = 'e'; break; + case 0x27: str[0] = 'f'; break; + case 0x21: str[0] = 'g'; break; + case 0x07: str[0] = 'h'; break; + case 0x04: str[0] = 'i'; break; + case 0x02: str[0] = 'j'; break; + case 0x23: str[0] = 'k'; break; + case 0x20: str[0] = 'l'; break; + case 0x26: str[0] = 'm'; break; + case 0x3C: str[0] = 'n'; break; + //added for monoclinic a + case 0x06: str[0] = 'o'; break; + case 0x01: str[0] = 'p'; break; + default: throw std::runtime_error("couldn't map matrix to EMsoft character"); + } + + //write translation characters + for(size_t i = 0; i < 3; i++) { + switch(t[i]) { + case 0: str[i+1] = 'O'; break;//no translation + case 4: str[i+1] = 'A'; break;// 1/6 + case 6: str[i+1] = 'B'; break;// 1/4 + case 8: str[i+1] = 'C'; break;// 1/3 + case 12: str[i+1] = 'D'; break;// 1/2 + case 16: str[i+1] = 'E'; break;// 2/3 + case 18: str[i+1] = 'F'; break;// 3/4 + case 20: str[i+1] = 'G'; break;// 5/6 + default: throw std::runtime_error("couldn't map translation to EMsoft character"); + } + } + } + } + + //////////////////////////////////////////////// + // detail (exposed primariily for testing) // + //////////////////////////////////////////////// + + //@brief : get a 3x3 position matrix from an index + //@param i: index of matrix to get, must be [0,64) + //@return : pointer to 3x3 matrix corresponding to i (row major) + int8_t const * GenPos::IdxToMat3(const int_fast8_t i) { + static const int8_t lut[64][9] = { + //m-3m general positions + { 1, 0, 0, 0, 1, 0, 0, 0, 1}, // 0x00 | x , y , z : 1 + {-1, 0, 0, 0, 1, 0, 0, 0, 1}, // 0x01 | -x , y , z : m_x + { 1, 0, 0, 0,-1, 0, 0, 0, 1}, // 0x02 | x ,-y , z : m_y + {-1, 0, 0, 0,-1, 0, 0, 0, 1}, // 0x03 | -x ,-y , z : 2 _z + { 1, 0, 0, 0, 1, 0, 0, 0,-1}, // 0x04 | x , y ,-z : m_z + {-1, 0, 0, 0, 1, 0, 0, 0,-1}, // 0x05 | -x , y ,-z : 2 _y + { 1, 0, 0, 0,-1, 0, 0, 0,-1}, // 0x06 | x ,-y ,-z : 2 _x + {-1, 0, 0, 0,-1, 0, 0, 0,-1}, // 0x07 | -x ,-y ,-z : -1 + + { 0, 0, 1, 1, 0, 0, 0, 1, 0}, // 0x08 | z , x , y : 3^+_{ 1 1 1} + { 0, 0,-1, 1, 0, 0, 0, 1, 0}, // 0x09 | -z , x , y : -3^+_{-1 1-1} + { 0, 0, 1, -1, 0, 0, 0, 1, 0}, // 0x0A | z ,-x , y : -3^+_{-1-1 1} + { 0, 0,-1, -1, 0, 0, 0, 1, 0}, // 0x0B | -z ,-x , y : 3^+_{ 1-1-1} + { 0, 0, 1, 1, 0, 0, 0,-1, 0}, // 0x0C | z , x ,-y : -3^+_{ 1-1-1} + { 0, 0,-1, 1, 0, 0, 0,-1, 0}, // 0x0D | -z , x ,-y : 3^+_{-1-1 1} + { 0, 0, 1, -1, 0, 0, 0,-1, 0}, // 0x0E | z ,-x ,-y : 3^+_{-1 1-1} + { 0, 0,-1, -1, 0, 0, 0,-1, 0}, // 0x0F | -z ,-x ,-y : -3^+_{ 1 1 1} + + { 0, 1, 0, 0, 0, 1, 1, 0, 0}, // 0x10 | y , z , x : 3^-_{ 1 1 1} + { 0,-1, 0, 0, 0, 1, 1, 0, 0}, // 0x11 | -y , z , x : -3^-_{-1-1 1} + { 0, 1, 0, 0, 0,-1, 1, 0, 0}, // 0x12 | y ,-z , x : -3^-_{ 1-1-1} + { 0,-1, 0, 0, 0,-1, 1, 0, 0}, // 0x13 | -y ,-z , x : 3^-_{-1 1-1} + { 0, 1, 0, 0, 0, 1, -1, 0, 0}, // 0x14 | y , z ,-x : -3^-_{-1 1-1} + { 0,-1, 0, 0, 0, 1, -1, 0, 0}, // 0x15 | -y , z ,-x : 3^-_{ 1-1-1} + { 0, 1, 0, 0, 0,-1, -1, 0, 0}, // 0x16 | y ,-z ,-x : 3^-_{-1-1 1} + { 0,-1, 0, 0, 0,-1, -1, 0, 0}, // 0x17 | -y ,-z ,-x : -3^-_{ 1 1 1} + + { 1, 0, 0, 0, 0, 1, 0, 1, 0}, // 0x18 | x , z , y : m _{ 0 1-1} + {-1, 0, 0, 0, 0, 1, 0, 1, 0}, // 0x19 | -x , z , y : 2 _{ 0 1 1} + { 1, 0, 0, 0, 0,-1, 0, 1, 0}, // 0x1A | x ,-z , y : 4^+_{ 1 0 0} + {-1, 0, 0, 0, 0,-1, 0, 1, 0}, // 0x1B | -x ,-z , y : -4^-_{ 1 0 0} + { 1, 0, 0, 0, 0, 1, 0,-1, 0}, // 0x1C | x , z ,-y : 4^-_{ 1 0 0} + {-1, 0, 0, 0, 0, 1, 0,-1, 0}, // 0x1D | -x , z ,-y : -4^+_{ 1 0 0} + { 1, 0, 0, 0, 0,-1, 0,-1, 0}, // 0x1E | x ,-z ,-y : m _{ 0 1 1} + {-1, 0, 0, 0, 0,-1, 0,-1, 0}, // 0x1F | -x ,-z ,-y : 2 _{ 0 1-1} + + { 0, 1, 0, 1, 0, 0, 0, 0, 1}, // 0x20 | y , x , z : m _{ 1-1 0} (also in hex) + { 0,-1, 0, 1, 0, 0, 0, 0, 1}, // 0x21 | -y , x , z : 4^+_{ 0 0 1} + { 0, 1, 0, -1, 0, 0, 0, 0, 1}, // 0x22 | y ,-x , z : 4^-_{ 0 0 1} + { 0,-1, 0, -1, 0, 0, 0, 0, 1}, // 0x23 | -y ,-x , z : m _{ 1 1 0} (also in hex) + { 0, 1, 0, 1, 0, 0, 0, 0,-1}, // 0x24 | y , x ,-z : 2 _{ 1 1 0} (also in hex) + { 0,-1, 0, 1, 0, 0, 0, 0,-1}, // 0x25 | -y , x ,-z : -4^-_{ 0 0 1} + { 0, 1, 0, -1, 0, 0, 0, 0,-1}, // 0x26 | y ,-x ,-z : -4^+_{ 0 0 1} + { 0,-1, 0, -1, 0, 0, 0, 0,-1}, // 0x27 | -y ,-x ,-z : 2 _{ 1-1 0} (also in hex) + + { 0, 0, 1, 0, 1, 0, 1, 0, 0}, // 0x28 | z , y , x : m _{-1 0 1} + { 0, 0,-1, 0, 1, 0, 1, 0, 0}, // 0x29 | -z , y , x : 4^-_{ 0 1 0} + { 0, 0, 1, 0,-1, 0, 1, 0, 0}, // 0x2A | z ,-y , x : 2 _{ 1 0 1} + { 0, 0,-1, 0,-1, 0, 1, 0, 0}, // 0x2B | -z ,-y , x : -4^+_{ 0 1 0} + { 0, 0, 1, 0, 1, 0, -1, 0, 0}, // 0x2C | z , y ,-x : 4^+_{ 0 1 0} + { 0, 0,-1, 0, 1, 0, -1, 0, 0}, // 0x2D | -z , y ,-x : m _{ 1 0 1} + { 0, 0, 1, 0,-1, 0, -1, 0, 0}, // 0x2E | z ,-y ,-x : -4^-_{ 0 1 0} + { 0, 0,-1, 0,-1, 0, -1, 0, 0}, // 0x2F | -z ,-y ,-x : 2 _{-1 0 1} + + //hexagonal general positions + { 1,-1, 0, 1, 0, 0, 0, 0, 1}, // 0x30 | x-y, x , z : 6^+_{ 0 0 1} + { 1,-1, 0, 1, 0, 0, 0, 0,-1}, // 0x31 | x-y, x ,-z : -3^-_{ 0 0 1} + {-1, 1, 0, -1, 0, 0, 0, 0, 1}, // 0x32 | -x+y,-x , z : 3^-_{ 0 0 1} + {-1, 1, 0, -1, 0, 0, 0, 0,-1}, // 0x33 | -x+y,-x ,-z : -6^+_{ 0 0 1} + + { 1,-1, 0, 0,-1, 0, 0, 0, 1}, // 0x34 | x-y,-y , z : m _{ 1 2 0} + { 1,-1, 0, 0,-1, 0, 0, 0,-1}, // 0x35 | x-y,-y ,-z : 2 _{ 1 0 0} + {-1, 1, 0, 0, 1, 0, 0, 0, 1}, // 0x36 | -x+y, y , z : m _{ 1 0 0} + {-1, 1, 0, 0, 1, 0, 0, 0,-1}, // 0x37 | -x+y, y ,-z : 2 _{ 1 2 0} + + { 1, 0, 0, 1,-1, 0, 0, 0, 1}, // 0x38 | x , x-y, z : m _{ 0 1 0} + { 1, 0, 0, 1,-1, 0, 0, 0,-1}, // 0x39 | x , x-y,-z : 2 _{ 2 1 0} + {-1, 0, 0, -1, 1, 0, 0, 0, 1}, // 0x3A | -x ,-x+y, z : m _{ 2 1 0} + {-1, 0, 0, -1, 1, 0, 0, 0,-1}, // 0x3B | -x ,-x+y,-z : 2 _{ 0 1 0} + + { 0,-1, 0, 1,-1, 0, 0, 0, 1}, // 0x3C | -y , x-y, z : 3^+_{ 0 0 1} + { 0,-1, 0, 1,-1, 0, 0, 0,-1}, // 0x3D | -y , x-y,-z : -6^-_{ 0 0 1} + { 0, 1, 0, -1, 1, 0, 0, 0, 1}, // 0x3E | y ,-x+y, z : 6^-_{ 0 0 1} + { 0, 1, 0, -1, 1, 0, 0, 0,-1}, // 0x3F | y ,-x+y,-z : -3^+_{ 0 0 1} + }; + return lut[i]; + } + + //@brief : get the index of a 3x3 position matrix + //@param m: 3x3 matrix to get index of + //@return : index of matrix m (such that IdxToMat3(index) has the same matrix as m) + //@note : returns 0xFF if m is invalid (not in 64 possible returns of IdxToMat3) + uint_fast8_t GenPos::Mat3ToIdx(int8_t const * const m) { + //m-3m general position matrices are permutations of {+/-1, 0, 0}, {0, +/-1, 0}, {0, 0, +/-1} + //6/mmm general position matrices not in m-3m all have {0,0,+/-1} in the last row + //that means everything the absolute value of the upper left 2x2 submatrix uniquely determines the location of 1's we just need to handle +/- + + //start by determining the layout of the 1s + uint_fast8_t idx = std::abs(m[0]) + std::abs(m[1]) * 2 + std::abs(m[3]) * 4 + std::abs(m[4]) * 8; + switch(idx) { + case 9: idx = 0x00; break;// { 1, 0, 0, 0, 1, 0, 0, 0, 1} + case 4: idx = 0x08; break;// { 0, 0, 1, 1, 0, 0, 0, 1, 0} + case 2: idx = 0x10; break;// { 0, 1, 0, 0, 0, 1, 1, 0, 0} + case 1: idx = 0x18; break;// { 1, 0, 0, 0, 0, 1, 0, 1, 0} + case 6: idx = 0x20; break;// { 0, 1, 0, 1, 0, 0, 0, 0, 1} + case 8: idx = 0x28; break;// { 0, 0, 1, 0, 1, 0, 1, 0, 0} + case 7: idx = 0x30; break;// { 1,-1, 0, 1, 0, 0, 0, 0, 1} + case 11: idx = 0x34; break;// { 1,-1, 0, 0, 1, 0, 0, 0, 1} + case 13: idx = 0x38; break;// { 1, 0, 0, 1,-1, 0, 0, 0, 1} + case 14: idx = 0x3C; break;// { 0, 1, 0, 1,-1, 0, 0, 0, 1} + default: return 0xFF; + }; + + //handle cubic and hexagonal type separately + if(idx < 0x30) {//cubic type (8 possibilities) + //determine if the 1 in each row is positive or negative + //the sum of each row should be +/-1 but may not be (we'll have to check for that later) + const int r[3] = { + (1 - (m[0] + m[1] + m[2])) / 2,// 0/1 if (m[0] + m[1] + m[2]) == +/-1 + (1 - (m[3] + m[4] + m[5])) ,// 0/2 " " + (1 - (m[6] + m[7] + m[8])) * 2,// 0/4 " " + }; + idx += uint_fast8_t(r[0] + r[1] + r[2]);//shift by [0,8) + } else {//hexagonal type (4 possibilities) + const int_fast8_t xy = idx < 0x38 ? m[0]-m[1] : m[3]-m[4];//determine if we should add 0/2 to the index (is the xy row x-y or y-x) + idx += uint_fast8_t( ( 3 - xy - m[8] ) / 2 );//also shift by 0/1 for +/-z + } + + //finally check if the matrix actually matches + return std::equal(m, m+9, IdxToMat3(idx)) ? idx : 0xFF; + } +} + +#endif//_position_h_ diff --git a/include/xtal/quaternion.hpp b/include/xtal/quaternion.hpp new file mode 100644 index 0000000..1cab5c7 --- /dev/null +++ b/include/xtal/quaternion.hpp @@ -0,0 +1,528 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019, William C. Lenthe * + * All rights reserved. * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _quaternion_h_ +#define _quaternion_h_ + +#include +#include + +namespace xtal { + + //////////////////////////////////////////////////////////////////////// + // Quaternion Wrapper Class // + //////////////////////////////////////////////////////////////////////// + + //POD struct instead of class + packed check allows casting from array of doubles to array of quats ([w1,x1,y1,z1,w2,x2,y2,z2,w3,x3,y3,z3] -> [q1,q2,q3]) + template + struct Quat { + Real w, x, y, z; + + //@brief: default constructor + Quat(); + + //@brief : construct from values + //@param vw: value of w + //@param vx: value of x + //@param vy: value of y + //@param vz: value of z + Quat(const Real vw, const Real vx, const Real vy, const Real vz); + + //@brief : construct a quaternion of zeros + //@return: (0, 0, 0, 0) + static Quat Zero (); + + //@brief : construct an identity quaternion + //@return: (1, 0, 0, 0) + static Quat Identity(); + + //@brief : get a pointer to the underlying data as {w, x, y, z} + //@return: pointer to data + Real *const data() ; + Real const*const data() const; + + //unary operations + Quat conj () const; + Quat inv () const; + Quat neg () const; + Quat cAbs () const; + Quat expl () const; + Quat normalize () const; + Real norm () const; + Real norm2 () const; + Quat operator- () const; + + //binary quaternion scalar operations + Quat& operator+=(const Real& s) ; + Quat& operator-=(const Real& s) ; + Quat& operator*=(const Real& s) ; + Quat& operator/=(const Real& s) ; + Quat operator+ (const Real& s) const; + Quat operator- (const Real& s) const; + Quat operator* (const Real& s) const; + Quat operator/ (const Real& s) const; + + //binary quaternion quaternion operations + Real dot (const Quat& q) const; + Quat& operator+=(const Quat& q) ; + Quat& operator-=(const Quat& q) ; + Quat& operator*=(const Quat& q) ; + Quat& operator/=(const Quat& q) ; + Quat operator+ (const Quat& q) const; + Quat operator- (const Quat& q) const; + Quat operator* (const Quat& q) const; + Quat operator/ (const Quat& q) const; + bool operator< (const Quat& q) const; + bool operator==(const Quat& q) const; + + //vector rotation + void rotateVector(Real const * const vIn, Real * const vOut) const; + + //string conversion + std::string to_string(const size_t dig = std::numeric_limits::digits10, const bool pos = false) const; + + //@brief : formatted output + //@param os: location to write formatted quaternion + //@param qu: quaternion or write + //@return : os with quaternion written to it + template friend std::ostream& operator<<(std::ostream& os, const Quat& qu); + }; + + //////////////////////////////////////////////////////////////////////////////////// + // underlying quaternion math functions (operate on pointer to wxyz) // + //////////////////////////////////////////////////////////////////////////////////// + + namespace quat { + + //////////////////////////////////////////////////////////////////////// + // Unary Quaternion Operations // + //////////////////////////////////////////////////////////////////////// + + //@brief : urnary quaternion operations with a quaternion result + //@param qIn : quaternion + //@param qOut: location to write operator(qIn) + template void conj (Real const * const qIn , Real * const qOut);//conjugate + template void inv (Real const * const qIn , Real * const qOut);//inverse + template void neg (Real const * const qIn , Real * const qOut);//negate + template void cAbs (Real const * const qIn , Real * const qOut);//element wise absolute value + template void expl (Real const * const qIn , Real * const qOut);//explement (positive w equivalent) + template void normalize(Real const * const qIn , Real * const qOut);//qIn / norm(qIn) + + //@brief : urnary quaternion operations with a scalar result + //@param qIn : quaternion + //@return : result + template Real norm (Real const * const qIn );//magnitude + template Real norm2 (Real const * const qIn );//squared magnitude + + //////////////////////////////////////////////////////////////////////// + // Binary Quaternion Operations // + //////////////////////////////////////////////////////////////////////// + + //@brief : binary operations between a quaternion and a scalar + //@param qIn : input quaternion + //@param s : scalar to element wise operate with + //@param qOut: location to write qIn (operator) s + template void scalarAdd(Real const * const qIn , Real const s , Real * const qOut); + template void scalarSub(Real const * const qIn , Real const s , Real * const qOut); + template void scalarMul(Real const * const qIn , Real const s , Real * const qOut); + template void scalarDiv(Real const * const qIn , Real const s , Real * const qOut); + + //@brief : binary element wise operations between 2 quaternions with a scalar result + //@param qIn1: first quaternion + //@param qIn2: second quaternion + //@return : qIn1 (operator) qIn2 + template Real dot (Real const * const qIn1, Real const * const qIn2 ); + template bool less (Real const * const qIn1, Real const * const qIn2 ); + template bool equal (Real const * const qIn1, Real const * const qIn2 ); + + //@brief : binary element wise operations between 2 quaternions with a quaternion result + //@param qIn1: first quaternion + //@param qIn2: second quaternion + //@param qOut: location to write qIn1 (operator) qIn2 + template void add (Real const * const qIn1, Real const * const qIn2, Real * const qOut); + template void sub (Real const * const qIn1, Real const * const qIn2, Real * const qOut); + template void mul (Real const * const qIn1, Real const * const qIn2, Real * const qOut);//proper quaternion multiplication + template void div (Real const * const qIn1, Real const * const qIn2, Real * const qOut);//proper quaternion division: such that qu * qr / qr == qu + + //////////////////////////////////////////////////////////////////////// + // Other Quaternion Functions // + //////////////////////////////////////////////////////////////////////// + + //@brief : actively rotate a vector by a quaternion (q * v * q.conj()) + //@param q : quaternion to rotate by + //@param vIn : vector to rotate + //@param vOut: location to write rotated vector + template void rotateVector(Real const * const q, Real const * const vIn, Real * const vOut); + + //@brief : convert quaternion to a nicely formatted string + //@param qu : quaternion to convert to string + //@param dig: significant digits to print with + //@param pos: should a '+' be used before positive numbers + //@return : nicely formatted string as w x y z + template std::string to_string(Real const * const qu, const size_t dig = std::numeric_limits::digits10, const bool pos = false); + } +} + +//////////////////////////////////////////////////////////////////////// +// Implementations // +//////////////////////////////////////////////////////////////////////// + +#include "constants.hpp" + +#include +#include +#include +#include +#include +#include + +namespace xtal { + + //////////////////////////////////////////////////////////////////////// + // Quaternion Class Members // + //////////////////////////////////////////////////////////////////////// + + //@brief: default constructor + template + Quat::Quat() { + static_assert(sizeof(Quat) == 4 * sizeof(Real), "Quaternion struct must be packed" ); + static_assert(std::is_floating_point::value , "Quaternion must be templated on floating point type"); + } + + //@brief : construct from values + //@param vw: value of w + //@param vx: value of x + //@param vy: value of y + //@param vz: value of z + template Quat::Quat(const Real vw, const Real vx, const Real vy, const Real vz) : w(vw), x(vx), y(vy), z(vz) {} + + //@brief : construct a quaternion of zeros + //@return: (0, 0, 0, 0) + template Quat Quat::Zero () {return Quat(0, 0, 0, 0);} + + //@brief : construct an identity quaternion + //@return: (1, 0, 0, 0) + template Quat Quat::Identity() {return Quat(1, 0, 0, 0);} + + //@brief : get a pointer to the underlying data as {w, x, y, z} + //@return: pointer to data + template Real *const Quat::data () {return (Real *const)this;} + template Real const*const Quat::data () const {return (Real const*const)this;} + + //unary operations + template Quat Quat::conj () const {Quat q; quat::conj (data(), q.data()); return q; } + template Quat Quat::inv () const {Quat q; quat::inv (data(), q.data()); return q; } + template Quat Quat::neg () const {Quat q; quat::neg (data(), q.data()); return q; } + template Quat Quat::cAbs () const {Quat q; quat::cAbs (data(), q.data()); return q; } + template Quat Quat::expl () const {Quat q; quat::expl (data(), q.data()); return q; } + template Quat Quat::normalize () const {Quat q; quat::normalize(data(), q.data()); return q; } + template Real Quat::norm () const { return quat::norm (data() ); } + template Real Quat::norm2 () const { return quat::norm2 (data() ); } + template Quat Quat::operator- () const { return neg ( ); } + + //binary quaternion scalar operations + template Quat& Quat::operator+=(const Real& s) { quat::scalarAdd(data(), s , data()); return *this;} + template Quat& Quat::operator-=(const Real& s) { quat::scalarSub(data(), s , data()); return *this;} + template Quat& Quat::operator*=(const Real& s) { quat::scalarMul(data(), s , data()); return *this;} + template Quat& Quat::operator/=(const Real& s) { quat::scalarDiv(data(), s , data()); return *this;} + template Quat Quat::operator+ (const Real& s) const {Quat q; quat::scalarAdd(data(), s , q.data()); return q; } + template Quat Quat::operator- (const Real& s) const {Quat q; quat::scalarSub(data(), s , q.data()); return q; } + template Quat Quat::operator* (const Real& s) const {Quat q; quat::scalarMul(data(), s , q.data()); return q; } + template Quat Quat::operator/ (const Real& s) const {Quat q; quat::scalarDiv(data(), s , q.data()); return q; } + + //binary quaternion quaternion operations + template Real Quat::dot (const Quat& q) const { return quat::dot (data(), q.data() ); } + template Quat& Quat::operator+=(const Quat& q) { quat::add (data(), q.data(), data()); return *this;} + template Quat& Quat::operator-=(const Quat& q) { quat::sub (data(), q.data(), data()); return *this;} + template Quat& Quat::operator*=(const Quat& q) { quat::mul (data(), q.data(), data()); return *this;} + template Quat& Quat::operator/=(const Quat& q) { quat::div (data(), q.data(), data()); return *this;} + template Quat Quat::operator+ (const Quat& q) const {Quat r; quat::add (data(), q.data(), r.data()); return r; } + template Quat Quat::operator- (const Quat& q) const {Quat r; quat::sub (data(), q.data(), r.data()); return r; } + template Quat Quat::operator* (const Quat& q) const {Quat r; quat::mul (data(), q.data(), r.data()); return r; } + template Quat Quat::operator/ (const Quat& q) const {Quat r; quat::div (data(), q.data(), r.data()); return r; } + template bool Quat::operator< (const Quat& q) const { return quat::less (data(), q.data() ); } + template bool Quat::operator==(const Quat& q) const { return quat::equal (data(), q.data() ); } + + //vector rotation + template void Quat::rotateVector(Real const * const vIn, Real * const vOut) const {quat::rotateVector(data(), vIn, vOut);} + + //string conversion + template std::string Quat::to_string(const size_t dig, const bool pos) const {return quat::to_string(data(), dig, pos);} + + //@brief : formatted output + //@param os: location to write formatted quaternion + //@param qu: quaternion or write + //@return : os with quaternion written to it + template std::ostream& operator<<(std::ostream& os, const Quat& qu) {return os << qu.to_string(6);} + + //////////////////////////////////////////////////////////////////////////////////// + // underlying quaternion math functions (operate on pointer to wxyz) // + //////////////////////////////////////////////////////////////////////////////////// + + namespace quat { + //////////////////////////////////////////////////////////////////////// + // Unary Quaternion Operations // + //////////////////////////////////////////////////////////////////////// + + //@brief : quaternion conjugate + //@param qIn : quaternion + //@param qOut: location to write conjugate(qIn) + template + void conj(Real const * const qIn, Real * const qOut) { + qOut[0] = qIn[0];//w is unchanged + std::transform(qIn+1, qIn+4, qOut+1, std::negate());//x, y, and z are negated + } + + //@brief : quaternion inverse + //@param qIn : quaternion + //@param qOut: location to write inverse(qIn) + template + void inv(Real const * const qIn, Real * const qOut) { + conj(qIn, qOut); + scalarDiv(qOut, norm2(qOut), qOut);//inv(q) = conj(q) / norm(q) + } + + //@brief : quaternion negate + //@param qIn : quaternion + //@param qOut: location to write -qIn + template + void neg (Real const * const qIn, Real * const qOut) { + std::transform(qIn, qIn+4, qOut, std::negate()); + } + + //@brief : element wise absolute value + //@param qIn : quaternion + //@param qOut: location to write element wise abs(qIn) + template + void cAbs(Real const * const qIn, Real * const qOut) { + std::transform(qIn, qIn+4, qOut, static_cast(&std::fabs)); + } + + //@brief : quaternion explement + //@param qIn : quaternion + //@param qOut: location to write operator(qIn) + //@note : complementary angles sum to 90, supplementary to 180, and explary to 360 + template + void expl(Real const * const qIn, Real * const qOut) { + if(std::signbit(qIn[0])) { + neg(qIn, qOut); + } else { + std::copy(qIn, qIn+4, qOut); + } + } + + //@brief : normalize a quaternion + //@param qIn : quaternion to normalize + //@param qOut: location to write normalized quaternion + template + void normalize(Real const * const qIn , Real * const qOut) { + scalarDiv(qIn, norm(qIn), qOut); + } + + //@brief : quaternion norm (absolute value) + //@param qIn : quaternion + //@return : norm + template + Real norm (Real const * const qIn ) { + return std::sqrt(norm2(qIn)); + } + //@brief : quaternion norm^2 (absolute value) + //@param qIn : quaternion + //@return : norm^2 + template + Real norm2 (Real const * const qIn ) { + return dot(qIn, qIn); + } + + //////////////////////////////////////////////////////////////////////// + // Binary Quaternion Operations // + //////////////////////////////////////////////////////////////////////// + + //@brief : element wise add to a quaternion + //@param qIn : input quaternion + //@param s : scalar to element wise operate with + //@param qOut: location to write qIn (operator) s + template + void scalarAdd(Real const * const qIn , Real const s , Real * const qOut) { + std::transform(qIn , qIn +4, qOut, [&s](const Real i){return i+s;}); + } + + //@brief : element wise subtract from to a quaternion + //@param qIn : input quaternion + //@param s : scalar to element wise operate with + //@param qOut: location to write qIn (operator) s + template + void scalarSub(Real const * const qIn , Real const s , Real * const qOut) { + std::transform(qIn , qIn +4, qOut, [&s](const Real i){return i-s;}); + } + + //@brief : element wise multiply with a quaternion + //@param qIn : input quaternion + //@param s : scalar to element wise operate with + //@param qOut: location to write qIn (operator) s + template + void scalarMul(Real const * const qIn , Real const s , Real * const qOut) { + std::transform(qIn , qIn +4, qOut, [&s](const Real i){return i*s;}); + } + + //@brief : element wise divide into a quaternion + //@param qIn : input quaternion + //@param s : scalar to element wise operate with + //@param qOut: location to write qIn (operator) s + template + void scalarDiv(Real const * const qIn , Real const s , Real * const qOut) { + std::transform(qIn , qIn +4, qOut, [&s](const Real i){return i/s;}); + } + + //@brief : element wise add 2 quaternions + //@param qIn1: first quaternion + //@param qIn2: second quaternion + //@param qOut: location to write qIn1 + qIn2 + template + void add (Real const * const qIn1, Real const * const qIn2, Real * const qOut) { + std::transform(qIn1, qIn1+4, qIn2, qOut, std::plus ()); + } + + //@brief : element wise subtract 2 quaternions + //@param qIn1: first quaternion + //@param qIn2: second quaternion + //@param qOut: location to write qIn1 - qIn2 + template + void sub (Real const * const qIn1, Real const * const qIn2, Real * const qOut) { + std::transform(qIn1, qIn1+4, qIn2, qOut, std::minus()); + } + + //@brief : multiply two quaternions + //@param qIn1: first quaternion + //@param qIn2: second quaternion + //@param qOut: location to write qIn1 * qIn2 + template + void mul (Real const * const qIn1, Real const * const qIn2, Real * const qOut) { + const Real w = qIn1[0]*qIn2[0] - qIn1[1]*qIn2[1] - qIn1[2]*qIn2[2] - qIn1[3]*qIn2[3] ; + const Real x = qIn1[0]*qIn2[1] + qIn1[1]*qIn2[0] + (qIn1[2]*qIn2[3] - qIn1[3]*qIn2[2]) * pijk; + const Real y = qIn1[0]*qIn2[2] + qIn1[2]*qIn2[0] + (qIn1[3]*qIn2[1] - qIn1[1]*qIn2[3]) * pijk; + const Real z = qIn1[0]*qIn2[3] + qIn1[3]*qIn2[0] + (qIn1[1]*qIn2[2] - qIn1[2]*qIn2[1]) * pijk; + qOut[0] = w; + qOut[1] = x; + qOut[2] = y; + qOut[3] = z; + } + + //@brief : divde two quaternions + //@param qIn1: first quaternion + //@param qIn2: second quaternion + //@param qOut: location to write qIn1 / qIn2 + template + void div (Real const * const qIn1, Real const * const qIn2, Real * const qOut) { + // qIn1 / qIn2 == qIn1 * conj(qIn2) / norm2(qIn2) + const Real v = qIn2[0]*qIn2[0] + qIn2[1]*qIn2[1] + qIn2[2]*qIn2[2] + qIn2[3]*qIn2[3] ; // norm2(qIn2) + const Real w = ( qIn1[0]*qIn2[0] + qIn1[1]*qIn2[1] + qIn1[2]*qIn2[2] + qIn1[3]*qIn2[3] ) / v; + const Real x = (-qIn1[0]*qIn2[1] + qIn1[1]*qIn2[0] - (qIn1[2]*qIn2[3] - qIn1[3]*qIn2[2]) * pijk) / v; + const Real y = (-qIn1[0]*qIn2[2] + qIn1[2]*qIn2[0] - (qIn1[3]*qIn2[1] - qIn1[1]*qIn2[3]) * pijk) / v; + const Real z = (-qIn1[0]*qIn2[3] + qIn1[3]*qIn2[0] - (qIn1[1]*qIn2[2] - qIn1[2]*qIn2[1]) * pijk) / v; + qOut[0] = w; + qOut[1] = x; + qOut[2] = y; + qOut[3] = z; + } + + //@brief : dot product of 2 quaternions (sum of element wise product) + //@param qIn1: first quaternion + //@param qIn2: second quaternion + //@return : qIn1.qIn2 + template + Real dot (Real const * const qIn1, Real const * const qIn2 ) { + return std::inner_product(qIn1, qIn1+4, qIn2, Real(0)); + } + + //@brief : check if a quaternion is less than another + //@param qIn1: first quaternion + //@param qIn2: second quaternion + //@return : qIn1 < qIn2 + //@note : lexicographical compare + template + bool less (Real const * const qIn1, Real const * const qIn2 ) { + return std::lexicographical_compare(qIn1, qIn1 + 4, qIn2, qIn2 + 4); + } + + //@brief : check if two quaternions are equal + //@param qIn1: first quaternion + //@param qIn2: second quaternion + //@return : qIn1 == qIn2 + template + bool equal (Real const * const qIn1, Real const * const qIn2 ) { + return std::equal(qIn1, qIn1 + 4, qIn2); + } + + //////////////////////////////////////////////////////////////////////// + // Other Quaternion Functions // + //////////////////////////////////////////////////////////////////////// + + //@brief : actively rotate a vector by a quaternion (q * v * q.conj()) + //@param q : quaternion to rotate by + //@param vIn : vector to rotate + //@param vOut: location to write rotated vector + template + void rotateVector(Real const * const q, Real const * const vIn, Real * const vOut) { + //q * v [stored locally since vIn/Out could overlap and vOut could only have 3 elements] + const Real w = -(q[1]*vIn[0] + q[2]*vIn[1] + q[3]*vIn[2]) ; + const Real x = q[0]*vIn[0] + (q[2]*vIn[2] - q[3]*vIn[1]) * pijk; + const Real y = q[0]*vIn[1] + (q[3]*vIn[0] - q[1]*vIn[2]) * pijk; + const Real z = q[0]*vIn[2] + (q[1]*vIn[1] - q[2]*vIn[0]) * pijk; + + //(q * v) * q.conj() [stored locally in case q and vOut overlap] + const Real vx = x*q[0] - w*q[1] + (z*q[2] - y*q[3]) * pijk; + const Real vy = y*q[0] - w*q[2] + (x*q[3] - z*q[1]) * pijk; + const Real vz = z*q[0] - w*q[3] + (y*q[1] - x*q[2]) * pijk; + + //save output + vOut[0] = vx; + vOut[1] = vy; + vOut[2] = vz; + } + + //@brief : convert quaternion to a nicely formatted string + //@param qu : quaternion to convert to string + //@param dig: significant digits to print with + //@param pos: should a '+' be used before positive numbers + //@return : nicely formatted string as w x y z + template + std::string to_string(Real const * const qu, const size_t dig, const bool pos) { + std::stringstream ss; + ss << std::fixed ;//decimal (vs scientific) output + ss << std::showpoint ;//always show the decimal for floating point numbers + ss << std::left ;//fill unused width to the right + ss << std::setfill('0');//pad with 0 instead of spaces + if(pos) ss << std::showpos; + if(!pos && !std::signbit(qu[0])) ss << ' ';//leave space so that '-' doesn't cause misalignment + ss << std::setw(dig) << qu[0] << ' '; + if(!pos && !std::signbit(qu[1])) ss << ' ';//leave space so that '-' doesn't cause misalignment + ss << std::setw(dig) << qu[1] << ' '; + if(!pos && !std::signbit(qu[2])) ss << ' ';//leave space so that '-' doesn't cause misalignment + ss << std::setw(dig) << qu[2] << ' '; + if(!pos && !std::signbit(qu[3])) ss << ' ';//leave space so that '-' doesn't cause misalignment + ss << std::setw(dig) << qu[3]; + return ss.str(); + } + } +} + +#endif//_quaternion_h_ diff --git a/include/xtal/rotations.hpp b/include/xtal/rotations.hpp new file mode 100644 index 0000000..5fe9633 --- /dev/null +++ b/include/xtal/rotations.hpp @@ -0,0 +1,1236 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019, William C. Lenthe * + * All rights reserved. * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _ROTATION_H_ +#define _ROTATION_H_ + +//orientation transform routines based on +// -Rowenhorst, David, et al. "Consistent Representations of and Conversions Between 3D Rotations." Model. Simul. Mater. Sci. Eng. 23.8 (2015): 083501. +// -Rosca, D., et al. "A New Method of Constructing a Grid in the Space of 3D rotations and its Applications to Texture Analysis." Model. Simul. Mater. Sci. Eng. 22.7 (2014): 075013. +// -fortran implementation of routines by Marc De Graef (https://github.com/marcdegraef/3Drotations) + +//the following conventions are used: +// -passive rotations +// -quaternions as [w, x, y, z] +// -0 <= rotation angle <= pi +// -rotation axis in positive z hemisphere for rotations of pi (+y for z == 0, 100 for y == z == 0) +// -rotation axis = [0, 0, 1] for rotations of 0 + +#include + +namespace xtal { + + //@brief : convert between 2 different orientation representations + //@param xx: input orientation (one of eu, om, ax, ro, qu, ho, or cu) + //@param yy: output orientation (one of eu, om, ax, ro, qu, ho, or cu) + //@note : xx and yy can be the same memory location + //@note : orientation representation abbreviations + // eu - ZXZ euler angles (Bunge convention, radians) + // om - orientation matrix (9 component vector in row major order) + // ax - axis angle (nx, ny, nz, w[radians]) + // ro - rodrigues vector (nx, ny, nz, tan(w/2)) + // qu - quaternion (w, x, y, z) + // ho - homochoric (x, y, z) + // cu - cubochoric (x, y, z) + template void eu2om(Real const * const eu, Real * const om);//A.1 + template void eu2ax(Real const * const eu, Real * const ax);//A.2 + template void eu2ro(Real const * const eu, Real * const ro);//A.3 + template void eu2qu(Real const * const eu, Real * const qu);//A.4 + template void eu2ho(Real const * const eu, Real * const ho);//eu->ax->ho + template void eu2cu(Real const * const eu, Real * const cu);//eu->ho->cu + template void om2eu(Real const * const om, Real * const eu);//A.5 + template void om2ax(Real const * const om, Real * const ax);//A.6 + template void om2ro(Real const * const om, Real * const ro);//om->eu->ro + template void om2qu(Real const * const om, Real * const qu);//A.7 + template void om2ho(Real const * const om, Real * const ho);//om->ax->ho + template void om2cu(Real const * const om, Real * const cu);//om->ho->cu + template void ax2eu(Real const * const ax, Real * const eu);//ax->om->eu + template void ax2om(Real const * const ax, Real * const om);//A.8 + template void ax2ro(Real const * const ax, Real * const ro);//A.9 + template void ax2qu(Real const * const ax, Real * const qu);//A.10 + template void ax2ho(Real const * const ax, Real * const ho);//A.11 + template void ax2cu(Real const * const ax, Real * const cu);//ax->ho->cu + template void ro2eu(Real const * const ro, Real * const eu);//ro->om->eu + template void ro2om(Real const * const ro, Real * const om);//ro->ax->om + template void ro2ax(Real const * const ro, Real * const ax);//A.12 + template void ro2qu(Real const * const ro, Real * const qu);//ro->ax->qu + template void ro2ho(Real const * const ro, Real * const ho);//A.13 + template void ro2cu(Real const * const ro, Real * const cu);//ro->ho->cu + template void qu2eu(Real const * const qu, Real * const eu);//A.13 + template void qu2om(Real const * const qu, Real * const om);//A.15 + template void qu2ax(Real const * const qu, Real * const ax);//A.16 + template void qu2ro(Real const * const qu, Real * const ro);//A.17 + template void qu2ho(Real const * const qu, Real * const ho);//A.18 + template void qu2cu(Real const * const qu, Real * const cu);//qu->ho->qu + template void ho2eu(Real const * const ho, Real * const eu);//ho->ax->eu + template void ho2om(Real const * const ho, Real * const om);//ho->ax->om + template void ho2ax(Real const * const ho, Real * const ax);//A.19 [I use Newton's method instead of the table from the paper] + template void ho2ro(Real const * const ho, Real * const ro);//ho->ax->ro + template void ho2qu(Real const * const ho, Real * const qu);//ho->ax->qu + template void ho2cu(Real const * const ho, Real * const qu); + template void cu2eu(Real const * const cu, Real * const eu);//cu->ho->eu + template void cu2om(Real const * const cu, Real * const om);//cu->ho->om + template void cu2ax(Real const * const cu, Real * const ax);//cu->ho->ax + template void cu2ro(Real const * const cu, Real * const ro);//cu->ho->ro + template void cu2qu(Real const * const cu, Real * const qu);//cu->ho->qu + template void cu2ho(Real const * const cu, Real * const qu); + + //@brief : convert from an orientation matrix to another + //@param om: input orientation (one of eu, om, ax, ro, qu, ho, or cu) + //@param yy: output orientation (one of eu, om, ax, ro, qu, ho, or cu) + //@note : orientation matrix as 3x3 array in row major order + template void om2eu(Real const * const * const om, Real * const eu); + template void om2ax(Real const * const * const om, Real * const ax); + template void om2ro(Real const * const * const om, Real * const ro); + template void om2qu(Real const * const * const om, Real * const qu); + template void om2ho(Real const * const * const om, Real * const ho); + template void om2cu(Real const * const * const om, Real * const cu); + + //@brief : convert between 2 different orientation representations + //@param xx: input orientation (one of eu, om, ax, ro, qu, ho, or cu) + //@param om: output orientation (one of eu, om, ax, ro, qu, ho, or cu) + //@note : orientation matrix as 3x3 array in row major order + template void eu2om(Real const * const eu, Real * const * const om); + template void ax2om(Real const * const ax, Real * const * const om); + template void ro2om(Real const * const ro, Real * const * const om); + template void qu2om(Real const * const qu, Real * const * const om); + template void ho2om(Real const * const ho, Real * const * const om); + template void cu2om(Real const * const cu, Real * const * const om); + + //@brief : convert ZYZ euler angles to quaternion + //@param eu: euler angles to convert to quaternion (Z, Y', Z'') + //@param qu: location to write quaternion as w, x, y, z + //@note : this is the standard wigner convention + //@note : equivalent to eu[0] -= pi/2 and eu[2] += pi/2 followed by eu2qu for ZXZ + template void zyz2qu(Real const * const eu, Real * const qu); + + //@brief : convert quaternion to ZYZ euler angles + //@param qu: quaternion to convert to euler angles as w, x, y, z + //@param eu: location to write euler angles (Z, Y', Z'') + //@note : this is the standard wigner convention + //@note : equivalent to qu2eu for ZXZ then eu[0] += pi/2 and eu[2] -= pi/2 + template void qu2zyz(Real const * const qu, Real * const eu); + + //@brief : convert between 2 different orientation representations + //@param xx: input orientation (one of eu, om, ax, ro, qu, ho, or cu) + //@param yy: output orientation (one of eu, om, ax, ro, qu, ho, or cu) + //@note : xx and yy can be the same memory location + //@note : orientation representation abbreviations + // eu - ZXZ euler angles (Bunge convention, radians) + // om - orientation matrix (9 component vector in row major order) + // ax - axis angle (nx, ny, nz, w[radians]) + // ro - rodrigues vector (nx, ny, nz, tan(w/2)) + // qu - quaternion (w, x, y, z) + // ho - homochoric (x, y, z) + // cu - cubochoric (x, y, z) + template void zyz2eu(Real const * const zyz, Real * const eu );//zyz[0] -= pi/2, zyz[2] += pi/2 + template void zyz2om(Real const * const zyz, Real * const om );//zyz->eu->om + template void zyz2ax(Real const * const zyz, Real * const ax );//zyz->eu->ax + template void zyz2ro(Real const * const zyz, Real * const ro );//zyz->eu->ro + template void zyz2ho(Real const * const zyz, Real * const ho );//zyz->eu->ax->ho + template void zyz2cu(Real const * const zyz, Real * const cu );//zyz->eu->ho->cu + template void eu2zyz(Real const * const eu , Real * const zyz);//eu[0] += pi/2, eu[2] -= pi/2 + template void om2zyz(Real const * const om , Real * const zyz);//om->eu->zyz + template void ax2zyz(Real const * const ax , Real * const zyz);//ax->om->eu->zyz + template void ro2zyz(Real const * const ro , Real * const zyz);//ro->om->eu->zyz + template void ho2zyz(Real const * const ho , Real * const zyz);//ho->ax->eu->zyz + template void cu2zyz(Real const * const cu , Real * const zyz);//cu->ho->eu->zyz + + //enumeration of possible possible rotation representations + enum class Rotation { + Unknown ,//bad/invalid + Euler ,//ZXZ euler angles (Bunge convention, radians) + Matrix ,//orientation matrix (9 component vector in row major order) + AxisAngle ,//axis angle (nx, ny, nz, w[radians]) + Rodrigues ,//rodrigues vector (nx, ny, nz, tan(w/2)) + Quaternion,//quaternion (w, x, y, z) + Homochoric,//homochoric (x, y, z) + Cubochoric,//cubochoric (x, y, z) + EulerZYZ ,//ZYZ euler angles + }; + + //@brief : get the number of values in a given representation + //@param rot: representatino to get length of + //@return : length of representation (e.g. 4 for quaternion) + //@note : returns 0 for Unknown + size_t rotLen(const Rotation& rot); + + //@brief: typedef for conversino functions + //@note : e.g. ConvFunc::type + template struct ConvFunc {typedef void(*type)(Real const * const, Real * const);}; + + //@brief : get a function pointer to convert between 2 representations + //@param in : input representation + //@param out: output representation + //@return : function pointer to convert from in => out (NULL if in or out is Unknown) + template typename ConvFunc::type getConv(const Rotation& in, const Rotation& out); +} + + +//@brief : print a string representation of the rotation type (e.g. 'eu' for Euler) +//@param os : ostream to write to +//@param rot: rotation type to write string representation of +//@return : os +std::ostream& operator<<(std::ostream& os, const xtal::Rotation& rot); + +//@brief : parse a string representation of the rotation type (e.g. 'eu' for Euler) +//@param is : istream to parse from to write to +//@param rot: rotation type to store result in +//@return : is +std::istream& operator>>(std::istream& is, xtal::Rotation& rot); + +//////////////////////////////////////////////////////////////////////// +// Implementations // +//////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +#include "constants.hpp" + +namespace xtal { + //////////////////////////////////////////////////////////////////////// + // Helper Functions // + //////////////////////////////////////////////////////////////////////// + namespace detail { + + //@brief: helper class to hold commonly used floating point constants + template + struct RotConst { + //make sure that the Real type has the appropriate attributes + static_assert(std::is_floating_point::value, "Real must be a floating point type");//disallow integers + + //constants for ho <--> cu conversion + static const Real k1;// = (Pi / 6) ^ (1 / 6); + static const Real k2;// = Pi / 12; + static const Real k3;// = sqrt(3 / Pi) * 2 ^ (3 / 4); + static const Real k4;// = sqrt(6 / Pi); + static const Real k5;// = sqrt(Pi / 24); + static const Real i1;// = 1 / k1 + static const Real i2;// = 1 / k2 + static const Real i4;// = 1 / k4 + }; + + template const Real RotConst::k1 = Real(0.897772786961286112895391569863263243784555740743629673879928327617808885); + template const Real RotConst::k2 = Real(0.261799387799149436538553615273291907016430783281258818414578716025651367); + template const Real RotConst::k3 = Real(1.64345640297250301250152526519400784863401933553126716815070358508401456 ); + template const Real RotConst::k4 = Real(1.38197659788534191706097858412755873255750948130497325394682941957806262 ); + template const Real RotConst::k5 = Real(0.361800627279133829681507313645397883936054447392272963488106163885184103); + + template const Real RotConst::i1 = Real(1.11386757821511262144383027557299082539112670911188377842237095371596563 ); + template const Real RotConst::i2 = Real(3.81971863420548805845321032094034468882703149777095476994401625741352314 ); + template const Real RotConst::i4 = Real(0.723601254558267659363014627290795767872108894784545926976212327770368205); + + //@brief : helper function to consistently select rotation axis for rotations of pi + //@param ax: axis to orient as {x, y, z} + template void orientAxis(Real * const ax) { + if(std::fabs(ax[2]) < Constants::rEps) {//z is 0 -> on equator + ax[2] = 0;//make z actually zero + if(std::fabs(ax[1]) < Constants::rEps) {//y and z are zero + ax[1] = 0;//make y actually zero + ax[0] = 1;//use 100 (not -100) + } else {//y is nonzero + const Real mag = std::copysign(std::sqrt(ax[0] * ax[0] + ax[1] * ax[1]), ax[1]);//renormalize since z was zerod + ax[0] /= mag;//normalize and use +y half of equator + ax[1] /= mag;//normalize and use +y half of equator + } + } else {//z is nonzero + if(std::signbit(ax[2])) std::transform(ax, ax + 3, ax, std::negate());//use northern hemisphere + } + } + + //@brief : inverse of ((3/4)*(x-sin(x)))^(2/3) via newton's method + //@param y: ((3/4)*(x-sin(x)))^(2/3) ) + //@return : x + //@note : this is the magnitude of the homochoric vector^2 + template + Real hoInv(Real y) { + //sanity check input + if(y < Real(0) || y >= Constants::hoR2 + Constants::thr) { + std::stringstream ss; + ss << "homochoric magnitude^2 " << y << " is outside of 0, (3*pi/4)^(2/3)]"; + throw std::domain_error(ss.str()); + } + + //handle edges + if(y >= Constants::hoR2 - Constants::thr) return Constants::pi; + else if(y <= Constants::thr) return Real(0); + + //newton iterate + Real x = Real(2) * std::acos(Real(1) - y / Real(2));//initial guess from small taylor expansion + y = std::sqrt(y); + Real prevErr = Constants::inf; + for(size_t i = 0; i < 16; i++) { + const Real fx = std::pow(Real(3) * (x - std::sin(x)) / Real(4), Real(1) / Real(3));//compute forward value + const Real delta = fx - y;//compute error + const Real err = std::fabs(delta); + if(0 == delta || err == prevErr) return x;//no error or flipping between +/- v + x -= Real(4) * fx * fx * delta / (Real(1) - std::cos(x));//update + if(err > prevErr) return x;//flipping between v / -2v + prevErr = err; + } + + //if we made it this far newton's method failed to converge + std::stringstream ss; + ss << "failed to invert ((3/4)*(x-sin(x)))^(2/3) for " << y; + throw std::runtime_error(ss.str()); + return 0; + } + + //@brief : sextant type for cubochoric <-> homochoric transformation symmetry + //@param v: vector to compute sextant of + //@return : sextant type + template + size_t pyramidType(Real const * const v) { + std::pair minMax = std::minmax_element(v, v+3); + const Real minEl = std::fabs(*(minMax.first)); + const Real maxEl = *(minMax.second); + return std::distance(v, minEl > maxEl ? minMax.first : minMax.second); + } + + //@brief : forward and reverse shuffling for cubochoric <-> homochoric transformation symmetry + //@param v: vector to shuffle + //@param p: pyramid type (from pyramidType(v)) + //@param u: true to unshuffle (undo a previous call of shufflePyramid(v, p)) + template + void shufflePyramid(Real * const v, const size_t p, bool u = false) { + if(p != 2) std::rotate(v, v + (u ? 2-p : p+1), v+3); + } + } + + //////////////////////////////////////////////////////////////////////// + // Direct Conversions // + //////////////////////////////////////////////////////////////////////// + + //@brief : convert from euler angle to orientation matrix + //@param eu: ZXZ euler angles in radians (Bunge) + //@param om: orientation matrix as row major 3x3 array + //@note : Rowenhorst et.al. A.1 + template void eu2om(Real const * const eu, Real * const * const om) { + //build matrix + const Real c0 = std::cos(eu[0]); + const Real c1 = std::cos(eu[1]); + const Real c2 = std::cos(eu[2]); + const Real s0 = std::sin(eu[0]); + const Real s1 = std::sin(eu[1]); + const Real s2 = std::sin(eu[2]); + om[0][0] = c0 * c2 - s0 * c1 * s2; + om[0][1] = s0 * c2 + c0 * c1 * s2; + om[0][2] = s1 * s2; + om[1][0] = -c0 * s2 - s0 * c1 * c2; + om[1][1] = -s0 * s2 + c0 * c1 * c2; + om[1][2] = s1 * c2; + om[2][0] = s0 * s1; + om[2][1] = -c0 * s1; + om[2][2] = c1; + + //zero out tiny entries + for(size_t i = 0; i < 3; i++) { + for(size_t j = 0; j < 3; j++) { + if(std::fabs(om[i][j]) <= Constants::thr) om[i][j] = Real(0); + } + } + } + + //@brief : convert from euler angle to axis angle axis angle + //@param eu: ZXZ euler angles in radians (Bunge) + //@param ax: axis angle pair as (nx, ny, nz, w [radians]) + //@note : Rowenhorst et.al. A.2 + template void eu2ax(Real const * const eu, Real * const ax) { + //compute initial values and handle tiny rotations + const Real t = std::tan(eu[1] / Real(2)); + const Real sigma = (eu[0] + eu[2]) / Real(2); + const Real s = std::sin(sigma); + const Real tau = std::sqrt(t * t + s * s); + if(tau <= Constants::thr * Real(2)) { + std::fill(ax, ax+4, Real(0)); + ax[2] = Real(1); + return; + } + + //compute axis + const Real delta = (eu[0] - eu[2]) / Real(2); + Real alpha = std::fabs(sigma - Constants::pi / Real(2)) <= Constants::thr ? Constants::pi : Real(2) * std::atan(tau / std::cos(sigma)); + const Real k = Real(-pijk) / std::copysign(tau, alpha); + ax[0] = k * std::cos(delta) * t; + ax[1] = k * std::sin(delta) * t; + ax[2] = k * std::sin(sigma); + + //normalize axis + const Real mag = std::sqrt(std::inner_product(ax, ax+3, ax, Real(0))); + std::transform(ax, ax+3, ax, [mag](const Real i){return i/mag;}); + + //handle ambiguous case (rotation angle of pi) + alpha = std::fabs(alpha); + if(alpha + Constants::thr >= Constants::pi) { + detail::orientAxis(ax); + ax[3] = Constants::pi; + } else if(alpha <= Constants::thr) { + std::fill(ax, ax+4, Real(0)); + ax[3] = Real(1); + } else { + ax[3] = alpha; + } + } + + //@brief : convert from euler angle to rodrigues + //@param eu: ZXZ euler angles in radians (Bunge) + //@param ro: rodrigues vector as (nx, ny, nz, tan(w/2)) + //@note : Rowenhorst et.al. A.3 + template void eu2ro(Real const * const eu, Real * const ro) { + eu2ax(eu, ro); + ro[3] = ro[3] == Constants::pi ? Constants::inf : std::tan(ro[3] / Real(2)); + } + + //@brief : convert from euler angle to quaternion + //@param eu: ZXZ euler angles in radians (Bunge) + //@param ro: quaternion as (w,x,y,z) + //@note : Rowenhorst et.al. A.4 + template void eu2qu(Real const * const eu, Real * const qu) { + const Real c = std::cos(eu[1] / Real(2)); + const Real s = std::sin(eu[1] / Real(2)); + const Real sigma = (eu[0] + eu[2]) / Real(2); + const Real delta = (eu[0] - eu[2]) / Real(2); + qu[0] = c * std::cos(sigma); + qu[1] = s * std::cos(delta) * Real(-pijk); + qu[2] = s * std::sin(delta) * Real(-pijk); + qu[3] = c * std::sin(sigma) * Real(-pijk); + if(qu[0] < Real(0)) std::transform(qu, qu+4, qu, std::negate()); + + //normalize + Real mag = std::sqrt(std::inner_product(qu, qu+4, qu, Real(0))); + std::transform(qu, qu+4, qu, [mag](const Real i){return i/mag;}); + + //handle ambiguous case (rotation angle of pi) + if(std::abs(qu[0]) <= Constants::thr) { + detail::orientAxis(qu+1); + qu[0] = Real(0); + } + } + + //@brief : convert from orientation matrix to euler angle + //@param om: orientation matrix as row major 3x3 array + //@param eu: ZXZ euler angles in radians (Bunge) + //@note : Rowenhorst et.al. A.5 + template void om2eu(Real const * const * const om, Real * const eu) { + if(std::fabs(om[2][2]) >= Real(1) - Constants::thr) { + if(om[2][2] > Real(0)) { + eu[0] = std::atan2( om[0][1], om[0][0]);//eu = [_, 0, _] + eu[1] = Real(0); + } else { + eu[0] = -std::atan2(-om[0][1], om[0][0]);//eu = [_, pi, _] + eu[1] = Constants::pi; + } + eu[2] = Real(0); + } else { + eu[1] = std::acos(om[2][2]); + const Real zeta = Real(1) / std::sqrt(Real(1) - om[2][2] * om[2][2]); + eu[0] = std::atan2(om[2][0] * zeta, -om[2][1] * zeta); + eu[2] = std::atan2(om[0][2] * zeta, om[1][2] * zeta); + } + std::for_each(eu, eu+3, [](Real& i){if(i < Real(0)) i += Constants::pi2;}); + } + + //@brief : convert from orientation matrix to axis angle + //@param om: orientation matrix as row major 3x3 array + //@param ax: axis angle pair as (nx, ny, nz, w [radians]) + //@note : Rowenhorst et.al. A.6 + template void om2ax(Real const * const * const om, Real * const ax) { + Real omega = (om[0][0] + om[1][1] + om[2][2] - Real(1)) / Real(2); + if(omega >= Real(1) - Constants::thr) { + std::fill(ax, ax+4, Real(0)); + ax[2] = Real(1); + return; + } + + //compute eigenvector for eigenvalue of 1 (cross product of 2 adjacent columns of A-y*I) + const Real om00 = om[0][0] - Real(1); + const Real om11 = om[1][1] - Real(1); + const Real om22 = om[2][2] - Real(1); + const Real vecs[3][3] = { + {om[1][0]*om[2][1] - om[2][0]* om11 , om[2][0]*om[0][1] - om00 *om[2][1], om00 * om11 - om[1][0]*om[0][1]}, + { om11 * om22 - om[2][1]*om[1][2], om[2][1]*om[0][2] - om[0][1]* om22 , om[0][1]*om[1][2] - om11 *om[0][2]}, + {om[1][2]*om[2][0] - om22 *om[1][0], om22 * om00 - om[0][2]*om[2][0], om[0][2]*om[1][0] - om[1][2]* om00 } + }; + + //select vector with largest magnitude + const Real mags[3] = { + std::sqrt(std::inner_product(vecs[0], vecs[0]+3, vecs[0], Real(0))), + std::sqrt(std::inner_product(vecs[1], vecs[1]+3, vecs[1], Real(0))), + std::sqrt(std::inner_product(vecs[2], vecs[2]+3, vecs[2], Real(0))) + }; + const size_t i = std::distance(mags, std::max_element(mags, mags+3)); + if(mags[i] <= Constants::thr) { + std::fill(ax, ax+4, Real(0)); + ax[2] = Real(1); + return; + } + const Real mag = mags[i]; + std::transform(vecs[i], vecs[i]+3, ax, [mag](const Real i){return i/mag;}); + + //handle ambiguous case (rotation of pi) + if(omega <= Constants::thr - Real(1)) { + detail::orientAxis(ax); + ax[3] = Constants::pi; + return; + } + + //check axis sign + ax[0] = std::copysign(ax[0], Real(pijk) * (om[2][1] - om[1][2])); + ax[1] = std::copysign(ax[1], Real(pijk) * (om[0][2] - om[2][0])); + ax[2] = std::copysign(ax[2], Real(pijk) * (om[1][0] - om[0][1])); + ax[3] = std::acos(omega); + } + + //@brief : convert from orientation matrix to quaternion + //@param om: orientation matrix as row major 3x3 array + //@param qu: quaternion as (w, x, y, z) + //@note : Rowenhorst et.al. A.7 + template void om2qu(Real const * const * const om, Real * const qu) { + qu[0] = Real(1) + om[0][0] + om[1][1] + om[2][2]; + qu[1] = Real(1) + om[0][0] - om[1][1] - om[2][2]; + qu[2] = Real(1) - om[0][0] + om[1][1] - om[2][2]; + qu[3] = Real(1) - om[0][0] - om[1][1] + om[2][2]; + + //handle ambiguous case (rotation of pi) + if(std::fabs(qu[0]) <= Constants::thr) { + om2ax(om, qu); + ax2qu(qu, qu); + return; + } + + //handle rotation of 0 + if(qu[0] <= Constants::thr - Real(2)) { + std::fill(qu+1, qu+4, Real(0)); + qu[0] = Real(1); + return; + } + + std::transform(qu, qu+4, qu, [](const Real i){return i <= Constants::thr ? Real(0) : Real(pijk) * std::sqrt(i) / Real(2);}); + if(Real(pijk) * om[1][2] > Real(pijk) * om[2][1]) qu[1] = -qu[1]; + if(Real(pijk) * om[2][0] > Real(pijk) * om[0][2]) qu[2] = -qu[2]; + if(Real(pijk) * om[0][1] > Real(pijk) * om[1][0]) qu[3] = -qu[3]; + + //ensure rotation angle <= pi + if(qu[0] < Real(0)) std::transform(qu, qu+4, qu, std::negate()); + + //normalize + Real mag = std::sqrt(std::inner_product(qu, qu+4, qu, Real(0))); + std::transform(qu, qu+4, qu, [mag](const Real i){return i/mag;}); + } + + //@brief : convert from axis angle to orientation matrix + //@param ax: axis angle pair as (nx, ny, nz, w [radians]) + //@param om: orientation matrix as row major 3x3 array + //@note : Rowenhorst et.al. A.8 + template void ax2om(Real const * const ax, Real * const * const om) { + if(std::fabs(ax[3]) <= Constants::thr) { + for(size_t i = 0; i < 3; i++) { + std::fill(om[i], om[i]+3, Real(0)); + om[i][i] = Real(1); + } + } else if(std::fabs(ax[3]) >= Constants::pi - Constants::thr) { + for(size_t i = 0; i < 3; i++) + om[i][i] =Real(2) * ax[i] * ax[i] - Real(1); + for(size_t i = 0; i < 3; i++) { + const size_t j = (i+1)%3; + const Real x = Real(2) * ax[i] * ax[j]; + om[i][j] = x; + om[j][i] = x; + } + } else { + const Real c = std::cos(ax[3]); + const Real s = std::sin(ax[3]); + const Real omc = Real(1) - c; + for(size_t i = 0; i < 3; i++) + om[i][i] = c + omc * ax[i] * ax[i]; + for(size_t i = 0; i < 3; i++) { + const size_t j = (i+1)%3; + const Real x = omc * ax[i] * ax[j]; + const Real y = Real(pijk) * s * ax[(i+2)%3]; + om[i][j] = x - y; + om[j][i] = x + y; + } + } + } + + //@brief : convert from axis angle to rodrigues vector + //@param ax: axis angle pair as (nx, ny, nz, w [radians]) + //@param ro: rodrigues vector as (nx, ny, nz, tan(w/2)) + //@note : Rowenhorst et.al. A.9 + template void ax2ro(Real const * const ax, Real * const ro) { + if(std::fabs(ax[3]) <= Constants::thr) { + std::fill(ro, ro+4, Real(0)); + ro[2] = Real(1); + } else { + std::copy(ax, ax+3, ro); + ro[3] = std::fabs(ax[3] - Constants::pi) <= Constants::thr ? Constants::inf : std::tan(ax[3] / Real(2)); + } + } + + //@brief : convert from axis angle to quaternion + //@param ax: axis angle pair as (nx, ny, nz, w [radians]) + //@param qu: quaternion as (w, x, y, z) + //@note : Rowenhorst et.al. A.10 + template void ax2qu(Real const * const ax, Real * const qu) { + if(std::fabs(ax[3]) <= Constants::thr) { + std::fill(qu, qu+4, Real(0)); + qu[0] = Real(1); + } else { + if(ax == qu) std::rotate(qu, qu+3, qu+4);//rotate_copy doesn't work if source and destination are the same + else std::rotate_copy(ax, ax+3, ax+4, qu); + const Real s = std::sin(qu[0] / Real(2)); + qu[0] = std::cos(qu[0] / Real(2)); + std::for_each(qu+1, qu+4, [s](Real& i){i*=s;}); + + //normalize + Real mag = std::sqrt(std::inner_product(qu, qu+4, qu, Real(0))); + std::for_each(qu, qu+4, [mag](Real& i){i/=mag;}); + } + } + + //@brief : convert from axis angle to homochoric vector + //@param ax: axis angle pair as (nx, ny, nz, w [radians]) + //@param ho: homochoric vector as (x, y, z) + //@note : Rowenhorst et.al. A.11 + template void ax2ho(Real const * const ax, Real * const ho) { + if(std::fabs(ax[3]) <= Constants::thr) { + std::fill(ho, ho+3, Real(0)); + } else { + const Real k = std::pow(Real(3) / Real(4) * ( ax[3] - std::sin(ax[3]) ), Real(1) / Real(3)); + std::transform(ax, ax+3, ho, [k](const Real i){return i*k;}); + } + } + + //@brief : convert from rodrigues vector to axis angle + //@param ro: rodrigues vector as (nx, ny, nz, tan(w/2)) + //@param ax: axis angle pair as (nx, ny, nz, w [radians]) + //@note : Rowenhorst et.al. A.12 + template void ro2ax(Real const * const ro, Real * const ax) { + if(std::fabs(ro[3]) <= Constants::thr) { + std::fill(ax, ax+4, Real(0)); + ax[2] = Real(1); + } else { + std::copy(ro, ro+3, ax); + ax[3] = ro[3] < Constants::inf ? Real(2) * std::atan(ro[3]) : Constants::pi; + } + } + + //@brief : convert from rodrigues vector to homochoric vector + //@param ro: rodrigues vector as (nx, ny, nz, tan(w/2)) + //@param ho: homochoric vector as (x, y, z) + //@note : Rowenhorst et.al. A.13 + template void ro2ho(Real const * const ro, Real * const ho) { + const Real t = ro[3] < Constants::inf ? Real(2) * std::atan(ro[3]) : Constants::pi; + if(std::fabs(t) <= Constants::thr) { + std::fill(ho, ho+3, Real(0)); + } else { + const Real k = std::pow(Real(3) * ( t - std::sin(t) ) / Real(4), Real(1) / Real(3)); + std::transform(ro, ro+3, ho, [k](const Real i){return i*k;}); + } + } + + //@brief : convert from quaternion to euler angle + //@param qu: quaternion as (w, x, y, z) + //@param eu: ZXZ euler angles in radians (Bunge) + //@note : Rowenhorst et.al. A.13 + template void qu2eu(Real const * const qu, Real * const eu) { + const Real q03 = qu[0] * qu[0] + qu[3] * qu[3]; + const Real q12 = qu[1] * qu[1] + qu[2] * qu[2]; + const Real chi = std::sqrt(q03 * q12); + if(chi <= Constants::thr) { + if(q12 <= Constants::thr){ + eu[0] = std::atan2(Real(-2*pijk) * qu[0] * qu[3], qu[0] * qu[0] - qu[3] * qu[3]); + eu[1] = Real(0); + } else { + eu[0] = std::atan2(Real( 2 ) * qu[1] * qu[2], qu[1] * qu[1] - qu[2] * qu[2]); + eu[1] = Constants::pi; + } + eu[2] = Real(0); + } else { + const Real y1 = qu[1] * qu[3] ;//can divide by chi (but atan2 is magnitude independent) + const Real y2 = qu[0] * qu[2] * pijk;//can divide by chi (but atan2 is magnitude independent) + const Real x1 =-qu[0] * qu[1] * pijk;//can divide by chi (but atan2 is magnitude independent) + const Real x2 = qu[2] * qu[3] ;//can divide by chi (but atan2 is magnitude independent) + eu[0] = std::atan2(y1 - y2, x1 - x2); + eu[1] = std::atan2(Real(2) * chi, q03 - q12); + eu[2] = std::atan2(y1 + y2, x1 + x2); + } + std::for_each(eu, eu+3, [](Real& i){if(i < Real(0)) i += Constants::pi2;}); + } + + //@brief : convert from quaternion to orientation matrix + //@param qu: quaternion as (w, x, y, z) + //@param om: orientation matrix as row major 3x3 array + //@note : Rowenhorst et.al. A.15 + template void qu2om(Real const * const qu, Real * const * const om) { + const Real qbar = qu[0] * qu[0] - qu[1] * qu[1] - qu[2] * qu[2] - qu[3] * qu[3]; + for(size_t i = 0; i < 3; i++) { + om[i][i] = qbar + Real(2) * qu[i+1] * qu[i+1]; + + const size_t j = (i+1)%3; + const Real x = Real(2) * qu[i+1] * qu[j+1]; + const Real y = Real(2) * Real(pijk) * qu[0] * qu[(i+2)%3+1]; + om[i][j] = x - y; + om[j][i] = x + y; + } + } + + //@brief : convert from quaternion to axis angle + //@param qu: quaternion as (w, x, y, z) + //@param ax: axis angle pair as (nx, ny, nz, w [radians]) + //@note : Rowenhorst et.al. A.16 + template void qu2ax(Real const * const qu, Real * const ax) { + const Real omega = Real(2) * std::acos(qu[0]); + if(omega <= Constants::thr) { + std::fill(ax, ax+4, Real(0)); + ax[2] = Real(1); + } else { + const Real s = std::copysign(Real(1) / std::sqrt(std::inner_product(qu+1, qu+4, qu+1, Real(0))), qu[0]); + std::transform(qu+1, qu+4, ax, [s](const Real i){return i*s;}); + if(omega >= Constants::pi - Constants::thr) { + detail::orientAxis(ax); + ax[3] = Constants::pi; + } else { + ax[3] = omega; + } + } + } + + //@brief : convert from quaternion to rodrigues vector + //@param qu: quaternion as (w, x, y, z) + //@param ro: rodrigues vector as (nx, ny, nz, tan(w/2)) + //@note : Rowenhorst et.al. A.17 + template void qu2ro(Real const * const qu, Real * const ro) { + if(qu[0] <= Constants::thr) { + std::copy(qu+1, qu+4, ro); + ro[3] = Constants::inf; + return; + } + const Real s = std::sqrt(std::inner_product(qu+1, qu+4, qu+1, Real(0))); + if(s <= Constants::thr) { + std::fill(ro, ro+4, Real(0)); + ro[2] = Real(1); + } else { + std::transform(qu+1, qu+4, ro, [s](const Real i){return i/s;}); + ro[3] = std::tan(std::acos(qu[0])); + } + } + + //@brief : convert from quaternion to homochoric vector + //@param qu: quaternion as (w, x, y, z) + //@param ho: homochoric vector as (x, y, z) + //@note : Rowenhorst et.al. A.18 + template void qu2ho(Real const * const qu, Real * const ho) { + const Real omega = Real(2) * std::acos(qu[0]); + if(std::fabs(omega) <= Constants::thr) { + std::fill(ho, ho+3, Real(0)); + } else { + const Real s = Real(1) / std::sqrt(std::inner_product(qu+1, qu+4, qu+1, Real(0))); + const Real k = std::pow(Real(3) * ( omega - std::sin(omega) ) / Real(4), Real(1) / Real(3)) * s; + std::transform(qu+1, qu+4, ho, [k](const Real i){return i*k;}); + } + } + + //@brief : convert from homochoric vector to axis angle + //@param ho: homochoric vector as (x, y, z) + //@param ax: axis angle pair as (nx, ny, nz, w [radians]) + //@note : Rowenhorst et.al. A.19 + template void ho2ax(Real const * const ho, Real * const ax) { + const Real mag2 = std::inner_product(ho, ho+3, ho, Real(0)); + if(mag2 <= Constants::thr) { + std::fill(ax, ax+4, Real(0)); + ax[2] = Real(1); + } else { + ax[3] = detail::hoInv(mag2); + const Real mag = std::sqrt(mag2); + std::transform(ho, ho+3, ax, [mag](const Real i){return i/mag;}); + if(ax[3] >= Constants::pi - Constants::thr) { + detail::orientAxis(ax); + ax[3] = Constants::pi; + } + } + } + + //////////////////////////////////////////////////////////////////////// + // Indirect Conversions // + //////////////////////////////////////////////////////////////////////// + + //no temporary storage required + template void eu2cu(Real const * const eu, Real * const cu) { eu2ho(eu, cu); ho2cu(cu, cu);} + template void om2ro(Real const * const * const om, Real * const ro) { om2eu(om, ro); eu2ro(ro, ro);} + template void om2cu(Real const * const * const om, Real * const cu) { om2ho(om, cu); ho2cu(cu, cu);} + template void ax2cu(Real const * const ax, Real * const cu) { ax2ho(ax, cu); ho2cu(cu, cu);} + template void ro2qu(Real const * const ro, Real * const qu) { ro2ax(ro, qu); ax2qu(qu, qu);} + template void ro2cu(Real const * const ro, Real * const cu) { ro2ho(ro, cu); ho2cu(cu, cu);} + template void qu2cu(Real const * const qu, Real * const cu) { qu2ho(qu, cu); ho2cu(cu, cu);} + template void ho2ro(Real const * const ho, Real * const ro) { ho2ax(ho, ro); ax2ro(ro, ro);} + template void ho2qu(Real const * const ho, Real * const qu) { ho2ax(ho, qu); ax2qu(qu, qu);} + template void cu2eu(Real const * const cu, Real * const eu) { cu2ho(cu, eu); ho2eu(eu, eu);} + template void cu2qu(Real const * const cu, Real * const qu) { cu2ho(cu, qu); ho2qu(qu, qu);} + template void cu2ax(Real const * const cu, Real * const ax) { cu2ho(cu, ax); ho2ax(ax, ax);} + + //temporary storage required + template void eu2ho(Real const * const eu, Real * const ho) {Real ax[4]; eu2ax(eu, ax); ax2ho(ax, ho);} + template void om2ho(Real const * const * const om, Real * const ho) {Real ax[4]; om2ax(om, ax); ax2ho(ax, ho);} + template void ax2eu(Real const * const ax, Real * const eu) {Real om[9]; ax2om(ax, om); om2eu(om, eu);} + template void ro2eu(Real const * const ro, Real * const eu) {Real om[9]; ro2om(ro, om); om2eu(om, eu);} + template void ro2om(Real const * const ro, Real * const * const om) {Real ax[4]; ro2ax(ro, ax); ax2om(ax, om);} + template void ho2eu(Real const * const ho, Real * const eu) {Real ax[4]; ho2ax(ho, ax); ax2eu(ax, eu);} + template void ho2om(Real const * const ho, Real * const * const om) {Real ax[4]; ho2ax(ho, ax); ax2om(ax, om);} + template void cu2om(Real const * const cu, Real * const * const om) {Real ho[3]; cu2ho(cu, ho); ho2om(ho, om);} + template void cu2ro(Real const * const cu, Real * const ro) {Real ho[3]; cu2ho(cu, ho); ho2ro(ho, ro);} + + //zyz conversions + template void zyz2om(Real const * const zyz, Real * const om) {zyz2eu(zyz, om); eu2om(om, om);}//zyz->eu->om + template void zyz2ax(Real const * const zyz, Real * const ax) {zyz2eu(zyz, ax); eu2ax(ax, ax);}//zyz->eu->ax + template void zyz2ro(Real const * const zyz, Real * const ro) {zyz2eu(zyz, ro); eu2ro(ro, ro);}//zyz->eu->ro + template void zyz2ho(Real const * const zyz, Real * const ho) {zyz2eu(zyz, ho); eu2ho(ho, ho);}//zyz->eu->ax->ho + template void zyz2cu(Real const * const zyz, Real * const cu) {zyz2eu(zyz, cu); eu2cu(cu, cu);}//zyz->eu->ho->cu + + template void om2zyz(Real const * const om, Real * const zyz) {om2eu(om, zyz); eu2zyz(zyz, zyz);}//om->eu->zyz + template void ax2zyz(Real const * const ax, Real * const zyz) {ax2eu(ax, zyz); eu2zyz(zyz, zyz);}//ax->om->eu->zyz + template void ro2zyz(Real const * const ro, Real * const zyz) {ro2eu(ro, zyz); eu2zyz(zyz, zyz);}//ro->om->eu->zyz + template void ho2zyz(Real const * const ho, Real * const zyz) {ho2eu(ho, zyz); eu2zyz(zyz, zyz);}//ho->ax->eu->zyz + template void cu2zyz(Real const * const cu, Real * const zyz) {cu2eu(cu, zyz); eu2zyz(zyz, zyz);}//cu->ho->eu->zyz + + + //////////////////////////////////////////////////////////////////////// + // Vectorized OM Wrappers // + //////////////////////////////////////////////////////////////////////// + + //@brief : convert from an orientation matrix to another + //@param om: input orientation (one of eu, om, ax, ro, qu, ho, or cu) + //@param yy: output orientation (one of eu, om, ax, ro, qu, ho, or cu) + //@note : orientation matrix as 3x3 array in row major order + // template void om2eu(Real const * const om, Real * const eu) {om2xx(&om2eu, om, eu);} + // template void om2ax(Real const * const om, Real * const ax) {om2xx(&om2ax, om, ax);} + // template void om2ro(Real const * const om, Real * const ro) {om2xx(&om2ro, om, ro);} + // template void om2qu(Real const * const om, Real * const qu) {om2xx(&om2qu, om, qu);} + // template void om2ho(Real const * const om, Real * const ho) {om2xx(&om2ho, om, ho);} + // template void om2cu(Real const * const om, Real * const cu) {om2xx(&om2cu, om, cu);} + + template void om2eu(Real const * const om, Real * const eu) {Real const * const m[3] = {om, om+3, om+6}; om2eu(m, eu);} + template void om2ax(Real const * const om, Real * const ax) {Real const * const m[3] = {om, om+3, om+6}; om2ax(m, ax);} + template void om2ro(Real const * const om, Real * const ro) {Real const * const m[3] = {om, om+3, om+6}; om2ro(m, ro);} + template void om2qu(Real const * const om, Real * const qu) {Real const * const m[3] = {om, om+3, om+6}; om2qu(m, qu);} + template void om2ho(Real const * const om, Real * const ho) {Real const * const m[3] = {om, om+3, om+6}; om2ho(m, ho);} + template void om2cu(Real const * const om, Real * const cu) {Real const * const m[3] = {om, om+3, om+6}; om2cu(m, cu);} + + //@brief : convert between 2 different orientation representations + //@param xx: input orientation (one of eu, om, ax, ro, qu, ho, or cu) + //@param om: output orientation (one of eu, om, ax, ro, qu, ho, or cu) + //@note : orientation matrix as 3x3 array in row major order + template void eu2om(Real const * const eu, Real * const om) {Real * const m[3] = {om, om+3, om+6}; eu2om(eu, m);} + template void ax2om(Real const * const ax, Real * const om) {Real * const m[3] = {om, om+3, om+6}; ax2om(ax, m);} + template void ro2om(Real const * const ro, Real * const om) {Real * const m[3] = {om, om+3, om+6}; ro2om(ro, m);} + template void qu2om(Real const * const qu, Real * const om) {Real * const m[3] = {om, om+3, om+6}; qu2om(qu, m);} + template void ho2om(Real const * const ho, Real * const om) {Real * const m[3] = {om, om+3, om+6}; ho2om(ho, m);} + template void cu2om(Real const * const cu, Real * const om) {Real * const m[3] = {om, om+3, om+6}; cu2om(cu, m);} + + //////////////////////////////////////////////////////////////////////// + // Cubochoric Conversions // + //////////////////////////////////////////////////////////////////////// + + //@brief : convert from cubochoric vector to homochoric vector + //@param cu: cubochoric vector as (x, y, z) + //@param ho: homochoric vector as (x, y, z) + //@note : Rosca, D., et al + template void cu2ho(Real const * const cu, Real * const ho) { + //get pyramid type, shuffle coordinates to +z pyramid, and check bounds + const size_t p = detail::pyramidType(cu); + std::copy(cu, cu+3, ho); + detail::shufflePyramid(ho, p); + if(std::fabs(ho[2]) >= Constants::cuA / Real(2) + Constants::thr) { + std::stringstream ss; + ss << "cubochoric component " << ho[2] << " is outside of +/-pi^(2/3)"; + throw std::domain_error(ss.str()); + } + + //handle origin + if(std::fabs(ho[2]) <= Constants::thr) { + std::fill(ho, ho+3, Real(0)); + return; + } + + //operation M1 + std::transform(ho, ho+3, ho, [](const Real i){return i*detail::RotConst::k1;}); + + //operation M2 + bool swapped = std::fabs(ho[0]) > std::fabs(ho[1]); + if(swapped) std::swap(ho[0], ho[1]); + if(std::fabs(ho[1]) >= Constants::thr) {//skip points along z axis to avoid divide by zero + const Real theta = detail::RotConst::k2 * ho[0] / ho[1]; + const Real k = detail::RotConst::k3 * ho[1] / std::sqrt(Constants::r2 - std::cos(theta)); + ho[0] = k * Constants::r2 * std::sin(theta); + ho[1] = k * Constants::r2 * std::cos(theta) - k; + if(swapped) std::swap(ho[0], ho[1]); + } else { + std::fill(ho, ho+2, Real(0)); + } + + //operation M3 + const Real ko = std::inner_product(ho, ho+2, ho, Real(0)); + const Real k = std::sqrt(Real(1) - Constants::pi * ko / (Real(24) * ho[2]*ho[2])); + std::transform(ho, ho+2, ho, [k](const Real i){return i*k;}); + ho[2] = detail::RotConst::k4 * ho[2] - ko * detail::RotConst::k5 / ho[2]; + + //unshuffle + detail::shufflePyramid(ho, p, true); + } + //@brief : convert from homochoric vector to cubochoric vector + //@param ho: homochoric vector as (x, y, z) + //@param cu: cubochoric vector as (x, y, z) + //@note : Rosca, D., et al + template void ho2cu(Real const * const ho, Real * const cu) { + //check bounds, get pyramid type, and shuffle coordinates to +z pyramid + std::copy(ho, ho+3, cu); + const Real rs = std::sqrt(std::inner_product(cu, cu+3, cu, Real(0))); + if(rs >= Constants::hoR + Constants::thr) { + std::stringstream ss; + ss << "homochoric magnitude " << rs << " is outside of 0, (3*pi/4)^(2/3)]"; + throw std::domain_error(ss.str()); + } + const size_t p = detail::pyramidType(cu); + detail::shufflePyramid(cu, p); + + //handle origin + if(rs <= Constants::thr) { + std::fill(cu, cu+3, Real(0)); + return; + } + + //invert operation M3 + const Real k = std::sqrt(Real(2) * rs / (rs + std::fabs(cu[2]))); + std::transform(cu, cu+2, cu, [k](const Real i){return i*k;}); + cu[2] = std::copysign(rs * detail::RotConst::i4, cu[2]); + + //invert operation M2 + Real x2 = cu[0] * cu[0]; + Real y2 = cu[1] * cu[1]; + const Real mag2 = x2 + y2; + if(mag2 >= Constants::thr) {//skip points along z axis + //only handle x <= y + bool swapped = std::fabs(cu[0]) > std::fabs(cu[1]); + if(swapped) { + std::swap(cu[0], cu[1]); + std::swap(x2, y2); + } + const Real x2y = mag2 + y2; + const Real rx2y = std::sqrt(x2y); + const Real kxy = std::sqrt( detail::RotConst::k2 * (x2y * mag2) / (x2y - std::fabs(cu[1]) * rx2y) ); + const Real ckx = (x2 + std::fabs(cu[1]) * rx2y) / Constants::r2 / mag2; + std::transform(cu, cu+2, cu, [kxy](const Real i){return std::copysign(kxy, i);}); + if(ckx >= Real(1)) + cu[0] = Real(0); + else if (ckx <= Real(-1)) + cu[0] *= Real(12); + else + cu[0] *= std::acos(ckx) * detail::RotConst::i2; + if(swapped) std::swap(cu[0], cu[1]); + } else { + std::fill(cu, cu+2, Real(0)); + } + + //invert operation M1 + std::transform(cu, cu+3, cu, [](const Real i){return i*detail::RotConst::i1;}); + + //unshuffle + detail::shufflePyramid(cu, p, true); + } + + //////////////////////////////////////////////////////////////////////// + // Nonstandard Euler Conversions // + //////////////////////////////////////////////////////////////////////// + + //@brief : convert ZYZ euler angles to quaternion + //@param eu: euler angles to convert to quaternion (Z, Y', Z'') + //@param qu: location to write quaternion as w, x, y, z + //@note : this is the standard wigner convention + //@note : equivalent to eu[0] -= pi/2 and eu[2] += pi/2 followed by eu2qu for ZXZ + //@note : derived by simplifying [cos(a/2), 0, 0, sin(a/2)*pijk] * [cos(b/2), 0, sin(b/2)*pijk, 0] * [cos(y/2), 0, 0, sin(y/2)*pijk] + template void zyz2qu(Real const * const eu, Real * const qu) { + const Real c = std::cos(eu[1] / Real(2)); + const Real s = std::sin(eu[1] / Real(2)); + const Real sigma = (eu[2] + eu[0]) / Real(2); + const Real delta = (eu[2] - eu[0]) / Real(2); + qu[0] = c * std::cos(sigma); + qu[1] = s * std::sin(delta) * Real(-pijk); + qu[2] = s * std::cos(delta) * Real(-pijk); + qu[3] = c * std::sin(sigma) * Real(-pijk); + if(std::signbit(qu[0])) std::transform(qu, qu+4, qu, std::negate());//restrict rotation to [0,pi] + + //handle ambiguous case (rotation angle of pi) + if(std::fabs(qu[0]) <= Constants::rEps) {//rotation is pi, restrict axis to +z hemisphere (+y semicircle on equator and 100 not -100) + qu[0] = 0;//zero out w + detail::orientAxis(qu+1); + } + } + + //@brief : convert quaternion to ZYZ euler angles + //@param qu: quaternion to convert to euler angles as w, x, y, z + //@param eu: location to write euler angles (Z, Y', Z'') + //@note : this is the standard wigner convention + //@note : equivalent to qu2eu for ZXZ then eu[0] += pi/2 and eu[2] -= pi/2 + template void qu2zyz(Real const * const qu, Real * const eu) { + const Real qu0 = qu[0] * Real(pijk); + const Real q03 = qu[0] * qu[0] + qu[3] * qu[3]; + const Real q12 = qu[1] * qu[1] + qu[2] * qu[2]; + const Real chi = std::sqrt(q03 * q12); + if(chi <= Constants::thr) { + if(q12 <= Constants::thr){ + eu[0] = std::atan2(-Real(2) * qu0 * qu[3], qu[0] * qu[0] - qu[3] * qu[3]); + eu[1] = Real(0); + } else { + eu[0] = std::atan2(-Real(2) * qu[1] * qu[2], qu[2] * qu[2] - qu[1] * qu[1]); + eu[1] = Constants::pi; + } + eu[2] = Real(0); + } else { + const Real y1 = qu[2] * qu[3];//can divide by chi (but atan2 is magnitude independent) + const Real y2 =-qu[1] * qu0 ;//can divide by chi (but atan2 is magnitude independent) + const Real x1 =-qu[2] * qu0 ;//can divide by chi (but atan2 is magnitude independent) + const Real x2 =-qu[1] * qu[3];//can divide by chi (but atan2 is magnitude independent) + eu[0] = std::atan2(y1 - y2, x1 - x2); + eu[1] = std::atan2(Real(2) * chi, q03 - q12); + eu[2] = std::atan2(y1 + y2, x1 + x2); + } + std::for_each(eu, eu+3, [](Real& i){if(i < Real(0)) i += Constants::pi2;}); + } + + //@brief: convert from ZYZ to ZXZ (Bunge) euler angles + //@param zyz: ZYZ euler angles + //@param eu : where to write ZXZ euler angles + template void zyz2eu(Real const * const zyz, Real * const eu ) { + eu[0] = zyz[0] - Constants::pi_2; + eu[1] = zyz[1] ; + eu[2] = zyz[2] + Constants::pi_2; + } + + //@brief: convert from ZYZ to ZXZ (Bunge) euler angles + //@param eu : ZXZ euler angles + //@param zyz: where to write ZYZ euler angles + template void eu2zyz(Real const * const eu , Real * const zyz) { + zyz[0] = eu[0] + Constants::pi_2; + zyz[1] = eu[1] ; + zyz[2] = eu[2] - Constants::pi_2; + } + + //@brief : get the number of values in a given representation + //@param rot: representatino to get length of + //@return : length of representation (e.g. 4 for quaternion) + //@note : returns 0 for Unknown + size_t rotLen(const Rotation& rot) { + switch(rot) { + case Rotation::Unknown : return 0; + case Rotation::Euler : return 3; + case Rotation::Matrix : return 9; + case Rotation::AxisAngle : return 4; + case Rotation::Rodrigues : return 4; + case Rotation::Quaternion: return 4; + case Rotation::Homochoric: return 3; + case Rotation::Cubochoric: return 3; + case Rotation::EulerZYZ : return 3; + } + throw std::logic_error("unhandled rotation type"); + } + + //@brief : get a function pointer to convert between 2 representations + //@param in : input representation + //@param out: output representation + //@return : function pointer to convert from in => out (NULL if in or out is Unknown) + template typename ConvFunc::type getConv(const Rotation& in, const Rotation& out) { + //build functions to copy various sizes (for e.g. eu2eu) + static const typename ConvFunc::type cpy3 = [](Real const * const in, Real * const out){std::copy(in, in+3, out);}; + static const typename ConvFunc::type cpy4 = [](Real const * const in, Real * const out){std::copy(in, in+4, out);}; + static const typename ConvFunc::type cpy9 = [](Real const * const in, Real * const out){std::copy(in, in+9, out);}; + + switch(in) { + case Rotation::Unknown : + switch(out) { + case Rotation::Unknown : return NULL; + case Rotation::Euler : return NULL; + case Rotation::Matrix : return NULL; + case Rotation::AxisAngle : return NULL; + case Rotation::Rodrigues : return NULL; + case Rotation::Quaternion: return NULL; + case Rotation::Homochoric: return NULL; + case Rotation::Cubochoric: return NULL; + case Rotation::EulerZYZ : return NULL; + } + + case Rotation::Euler : + switch(out) { + case Rotation::Unknown : return NULL; + case Rotation::Euler : return cpy3; + case Rotation::Matrix : return eu2om ; + case Rotation::AxisAngle : return eu2ax ; + case Rotation::Rodrigues : return eu2ro ; + case Rotation::Quaternion: return eu2qu ; + case Rotation::Homochoric: return eu2ho ; + case Rotation::Cubochoric: return eu2cu ; + case Rotation::EulerZYZ : return eu2zyz; + } + + case Rotation::Matrix : + switch(out) { + case Rotation::Unknown : return NULL; + case Rotation::Euler : return om2eu ; + case Rotation::Matrix : return cpy9; + case Rotation::AxisAngle : return om2ax ; + case Rotation::Rodrigues : return om2ro ; + case Rotation::Quaternion: return om2qu ; + case Rotation::Homochoric: return om2ho ; + case Rotation::Cubochoric: return om2cu ; + case Rotation::EulerZYZ : return om2zyz; + } + + case Rotation::AxisAngle : + switch(out) { + case Rotation::Unknown : return NULL; + case Rotation::Euler : return ax2eu ; + case Rotation::Matrix : return ax2om ; + case Rotation::AxisAngle : return cpy4; + case Rotation::Rodrigues : return ax2ro ; + case Rotation::Quaternion: return ax2qu ; + case Rotation::Homochoric: return ax2ho ; + case Rotation::Cubochoric: return ax2cu ; + case Rotation::EulerZYZ : return ax2zyz; + } + + case Rotation::Rodrigues : + switch(out) { + case Rotation::Unknown : return NULL; + case Rotation::Euler : return ro2eu ; + case Rotation::Matrix : return ro2om ; + case Rotation::AxisAngle : return ro2ax ; + case Rotation::Rodrigues : return cpy4; + case Rotation::Quaternion: return ro2qu ; + case Rotation::Homochoric: return ro2ho ; + case Rotation::Cubochoric: return ro2cu ; + case Rotation::EulerZYZ : return ro2zyz; + } + + case Rotation::Quaternion: + switch(out) { + case Rotation::Unknown : return NULL; + case Rotation::Euler : return qu2eu ; + case Rotation::Matrix : return qu2om ; + case Rotation::AxisAngle : return qu2ax ; + case Rotation::Rodrigues : return qu2ro ; + case Rotation::Quaternion: return cpy4; + case Rotation::Homochoric: return qu2ho ; + case Rotation::Cubochoric: return qu2cu ; + case Rotation::EulerZYZ : return qu2zyz; + } + + case Rotation::Homochoric: + switch(out) { + case Rotation::Unknown : return NULL; + case Rotation::Euler : return ho2eu ; + case Rotation::Matrix : return ho2om ; + case Rotation::AxisAngle : return ho2ax ; + case Rotation::Rodrigues : return ho2ro ; + case Rotation::Quaternion: return ho2qu ; + case Rotation::Homochoric: return cpy3; + case Rotation::Cubochoric: return ho2cu ; + case Rotation::EulerZYZ : return ho2zyz; + } + + case Rotation::Cubochoric: + switch(out) { + case Rotation::Unknown : return NULL; + case Rotation::Euler : return cu2eu ; + case Rotation::Matrix : return cu2om ; + case Rotation::AxisAngle : return cu2ax ; + case Rotation::Rodrigues : return cu2ro ; + case Rotation::Quaternion: return cu2qu ; + case Rotation::Homochoric: return cu2ho ; + case Rotation::Cubochoric: return cpy3; + case Rotation::EulerZYZ : return cu2zyz; + } + + case Rotation::EulerZYZ : + switch(out) { + case Rotation::Unknown : return NULL; + case Rotation::Euler : return zyz2eu ; + case Rotation::Matrix : return zyz2om ; + case Rotation::AxisAngle : return zyz2ax ; + case Rotation::Rodrigues : return zyz2ro ; + case Rotation::Quaternion: return zyz2qu ; + case Rotation::Homochoric: return zyz2ho ; + case Rotation::Cubochoric: return zyz2cu ; + case Rotation::EulerZYZ : return cpy3; + } + } + } +} + +//@brief : print a string representation of the rotation type +//@param os : ostream to write to +//@param rot: rotation type to write string representation of +//@return : os +std::ostream& operator<<(std::ostream& os, const xtal::Rotation& rot) { + switch(rot) { + case xtal::Rotation::Unknown : return os << "unk"; + case xtal::Rotation::Euler : return os << "eu"; + case xtal::Rotation::Matrix : return os << "om"; + case xtal::Rotation::AxisAngle : return os << "ax"; + case xtal::Rotation::Rodrigues : return os << "ro"; + case xtal::Rotation::Quaternion: return os << "qu"; + case xtal::Rotation::Homochoric: return os << "ho"; + case xtal::Rotation::Cubochoric: return os << "cu"; + case xtal::Rotation::EulerZYZ : return os << "zyz"; + } + throw std::logic_error("unhandled rotation type"); +} + +//@brief : parse a string representation of the rotation type (e.g. 'eu' for Euler) +//@param is : istream to parse from to write to +//@param rot: rotation type to store result in +//@return : is +std::istream& operator>>(std::istream& is, xtal::Rotation& rot) { + char nm[2]; + is >> nm[0] >> nm[1]; + if ('e' == nm[0] && 'u' == nm[1]) rot = xtal::Rotation::Euler ; + else if('o' == nm[0] && 'm' == nm[1]) rot = xtal::Rotation::Matrix ; + else if('a' == nm[0] && 'x' == nm[1]) rot = xtal::Rotation::AxisAngle ; + else if('r' == nm[0] && 'o' == nm[1]) rot = xtal::Rotation::Rodrigues ; + else if('q' == nm[0] && 'u' == nm[1]) rot = xtal::Rotation::Quaternion; + else if('h' == nm[0] && 'o' == nm[1]) rot = xtal::Rotation::Homochoric; + else if('c' == nm[0] && 'u' == nm[1]) rot = xtal::Rotation::Cubochoric; + else if('z' == nm[0] && 'y' == nm[1]) { + is >> nm[1]; + if('z' == nm[1]) { + rot = xtal::Rotation::EulerZYZ ; + }else { + rot = xtal::Rotation::Unknown ; + } + } else { + rot = xtal::Rotation::Unknown; + } + return is; +} + +#endif//_ROTATION_H_ diff --git a/include/xtal/sphere_sector.hpp b/include/xtal/sphere_sector.hpp new file mode 100644 index 0000000..e051fec --- /dev/null +++ b/include/xtal/sphere_sector.hpp @@ -0,0 +1,923 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019, William C. Lenthe * + * All rights reserved. * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _sphere_sector_h_ +#define _sphere_sector_h_ + +#include +#include +#include + +#include + +namespace xtal { + namespace fs { + //@brief : reduce a direction to the fundamental sector + //@param n : unit direction to reduce (magnitude is assumed to be 1) [in place] + //@return : true/false if n was/wasn't already in the fundamental sector + template inline bool b1 (Real * const n);//-1 + template inline bool _211 (Real * const n);//211 + template inline bool _211r(Real * const n);//211 rotated 45 @ 2 (2 fold @ xy) + template inline bool _121 (Real * const n);//121 + template inline bool _112 (Real * const n);//112 + template inline bool _m11 (Real * const n);//m11 + template inline bool _1m1 (Real * const n);//1m1 + template inline bool _11m (Real * const n);//11m + template inline bool _12m1(Real * const n);//12/m1 + template inline bool _112m(Real * const n);//112/m + template inline bool _222 (Real * const n);//222 + template inline bool _222r(Real * const n);//222r + template inline bool mm2 (Real * const n);//mm2 + template inline bool mm2r(Real * const n);//mm2r + template inline bool mmm (Real * const n);//mmm + template inline bool mmmr(Real * const n);//mmmr + template inline bool _4 (Real * const n);//4 + template inline bool b4 (Real * const n);//-4 + template inline bool _4m (Real * const n);//4/m + template inline bool _422 (Real * const n);//422 + template inline bool _4mm (Real * const n);//4mm + template inline bool b42m (Real * const n);//-42m + template inline bool b4m2 (Real * const n);//-4m2 + template inline bool _4mmm(Real * const n);//4/mmm + template inline bool _3 (Real * const n);//3 + template inline bool _3r (Real * const n);//3 rotated 30 degrees (use for other sectors) + template inline bool b3 (Real * const n);//-3 + template inline bool _321 (Real * const n);//321 + template inline bool _312 (Real * const n);//312 + template inline bool _3m1 (Real * const n);//3m1 + template inline bool _31m (Real * const n);//31m + template inline bool b3m1 (Real * const n);//-3m1 + template inline bool b31m (Real * const n);//-31m + template inline bool _6 (Real * const n);//6 + template inline bool b6 (Real * const n);//-6 + template inline bool _6m (Real * const n);//6/m + template inline bool _622 (Real * const n);//622 + template inline bool _6mm (Real * const n);//6mm + template inline bool b6m2 (Real * const n);//-6m2 + template inline bool b62m (Real * const n);//-62m + template inline bool _6mmm(Real * const n);//6/mmm + template inline bool _23 (Real * const n);//23 + template inline bool mb3 (Real * const n);//m3 + template inline bool _432 (Real * const n);//432 + template inline bool b43m (Real * const n);//-43m + template inline bool mb3m(Real * const n);//m3m + } + + //@brief: phenomenologically adjusted hsl2rgb + //@param hsl: hue, saturation, lightness [0,1] (saturation is assumed to be 1) + //@param rgb: location to write rgb [0,1] + //@reference: Nolze, G., & Hielscher, R. (2016). Orientations–perfectly colored. Journal of Applied Crystallography, 49(5), 1786-1802. + template void sph2rgb(Real const * const hsl, Real * const rgb); + + //spherical triangle -> color mappings + // -Nolze, Gert and Hielscher Ralf. "Orientations Perfectly Colors." J. Appl. Crystallogr. 49.5 (2016): 1786-1802. + namespace detail { + //@brief: helper class for mapping a polygon on the sphere to the unit hemisphere + template + struct SphericalPatch { + //@brief : construct a spherical triangle patch to map to the unit hemisphere + //@param verts: vertices of spherical patch (maps to evenly spaced points to equator) + //@param ctr : center of spherical patch (maps to north pole) + //@note : verts in CCW order + SphericalPatch(const Real verts[N][3], const Real ctr[3]) {build(verts, ctr);} + + //@brief : convert a unit direction inside the patch to fractional polar coordinates on the hemisphere + //@param n : unit direction to color + //@param tht: fractional azimuthal angle [0,1] maps to [0,2*pi] + //@param phi: fractional polar angle [0,1] maps to [0,pi/2] + void toHemi(Real const * const n, Real& tht, Real& phi) const; + + //@brief : compute coloring for a unit direction in the fundamental sector + //@param n : unit direction in fundamental sector (undefined behavior for directions outside of sector) + //@param rgb: location to write color [0,1] + //@param h2r: hsl2rgb like coloring function to use with h, s, and l in [0,1] and output as [0,1] rgb: void(Real const * const hsl, Real * const rgb) + //@param wCn: white/black center (north/south hemisphere) + //@param nTh: should theta be negated (to create enatiomorph / reverse color progression) + void toColor(Real const * const n, Real * const rgb, std::function h2r, const bool wCn = true, const bool nTh = false) const; + + protected: + //@brief : construct a spherical triangle patch to map to the unit hemisphere + //@param verts: vertices of spherical patch (maps to evenly spaced points to equator) + //@param ctr : center of spherical patch (maps to north pole) + //@param filF : fillet fraction, should be 0 for original paper, ~0.05 for perceptually uniform, must be in [0,0.5) + //@note : verts in CCW order + void build(const Real verts[N][3], const Real ctr[3], const Real filF = Real(0.01)); + + SphericalPatch() {} + + private: + Real rx [N];//direction of red from center + Real ry [N];//direction perpendicular to rx and center + Real center [N];//centroid of spherical triangle + Real normals [N][3];//normals of spherical triangle edges + Real cutoffs [N *3];//cutoff angles (start/end of fillets and angles of vertices) + Real coeffs [N][4];//fillet polynomial coefficients + Real cumAngles[ N+1];//cumulative angles of triangle vertices + std::vector omega ;//lookup table for nonlinear hue adjustment + }; + + //@brief: specialization for spherical triangles + template < typename Real> + struct SphericalTriangle : public SphericalPatch<3, Real> { + //@brief : construct a spherical triangle patch for IPF coloring + //@param nRed : unit direction to color red + //@param nGreen: unit direction to color green + //@param nBlue : unit direction to color blue + //@note : defines triangular patch in CCW order + SphericalTriangle(Real const*const nRed, Real const*const nGreen, Real const*const nBlue); + }; + + //@brief: specialization for spherical wedges + template < typename Real> + struct SphericalWedge : public SphericalPatch<4, Real> { + //@brief : construct a spherical triangle patch for IPF coloring + //@param nGreen: 2D unit direction to color green + //@param nBlue : 2D unit direction to color blue + //@note : red is assumed to be at [0,0,1] + SphericalWedge(Real const*const nGreen, Real const*const nBlue); + }; + } +} + +//////////////////////////////////////////////////////////////////////// +// Implementation Details // +//////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "constants.hpp" + +namespace xtal { + + //////////////////////////////////////////////////////////////////////// + // Fundamental Sector Reductions // + //////////////////////////////////////////////////////////////////////// + + namespace fs { + //@brief : rotate a 2d vector in place + //@param xy: vector to rotate + //@param c : cosine of rotation angle + //@param s : sine of rotation angle + template inline void rot2d(Real * const xy, const Real c, const Real s) { + const Real xp = c * xy[0] - s * xy[1]; + const Real yp = c * xy[1] + s * xy[0]; + xy[0] = xp; + xy[1] = yp; + } + + //@brief : mirror a 2d vector in place + //@param xy: vector to mirror + //@param c : cosine of mirror plane angle + //@param s : sine of mirror plane angle + template inline void mir2d(Real * const xy, const Real c, const Real s) { + //compute nearest point to xy on mirror plane + const Real d = c * xy[0] + s * xy[1]; + const Real pt[2] = {d * c, d * s}; + + //reflect + xy[0] = pt[0] - (xy[0] - pt[0]); + xy[1] = pt[1] - (xy[1] - pt[1]); + } + + //@brief : move a point in the first octant to the maximum z rotation possible by 120 @ 111 + //@param n: vector to rotate [in place] + //@return : true if z was already maximize, false otherwise + template inline bool r111(Real * const n) { + //get max and return if we're done + const size_t idx = std::distance(n, std::max_element(n, n+3)); + if(n[2] == n[idx]) return true; + std::rotate(n, n+1+idx, n+3); + return false; + } + + //@brief : reduce a direction to the -1 fundamental sector + //@param n : unit direction to reduce [in place] + //@return : true/false if n was/wasn't already in the fundamental sector + template + inline bool b1 (Real * const n) { + const bool ret = std::signbit(n[2]); + if(ret) { + n[0] = -n[0]; + n[1] = -n[1]; + n[2] = -n[2]; + } + return !ret; + } + + //@brief : reduce a direction to the 211 fundamental sector + //@param n : unit direction to reduce [in place] + //@return : true/false if n was/wasn't already in the fundamental sector + template + inline bool _211 (Real * const n) { + const bool ret = std::signbit(n[2]); + if(ret) { + n[1] = -n[1]; + n[2] = -n[2]; + } + return !ret; + } + + //@brief : reduce a direction to the 211 fundamental sector rotated 45 @ z + //@param n : unit direction to reduce [in place] + //@return : true/false if n was/wasn't already in the fundamental sector + template + inline bool _211r(Real * const n) { + const bool ret = std::signbit(n[2]); + if(ret) { + std::swap(n[0], n[1]); + n[2] = -n[2]; + } + return !ret; + } + + //@brief : reduce a direction to the 121 fundamental sector + //@param n : unit direction to reduce [in place] + //@return : true/false if n was/wasn't already in the fundamental sector + template + inline bool _121 (Real * const n) { + const bool ret = std::signbit(n[2]); + if(ret) { + n[0] = -n[0]; + n[2] = -n[2]; + } + return !ret; + } + + //@brief : reduce a direction to the 112 fundamental sector + //@param n : unit direction to reduce [in place] + //@return : true/false if n was/wasn't already in the fundamental sector + template + inline bool _112 (Real * const n) { + const bool ret = std::signbit(n[1]); + if(ret) { + n[0] = -n[0]; + n[1] = -n[1]; + } + return !ret; + } + + //@brief : reduce a direction to the m11 fundamental sector + //@param n : unit direction to reduce [in place] + //@return : true/false if n was/wasn't already in the fundamental sector + template + inline bool _m11 (Real * const n) { + const bool ret = std::signbit(n[0]); + if(ret) n[0] = -n[0]; + return !ret; + } + + //@brief : reduce a direction to the 1m1 fundamental sector + //@param n : unit direction to reduce [in place] + //@return : true/false if n was/wasn't already in the fundamental sector + template + inline bool _1m1 (Real * const n) { + const bool ret = std::signbit(n[1]); + if(ret) n[1] = -n[1]; + return !ret; + } + + //@brief : reduce a direction to the 11m fundamental sector + //@param n : unit direction to reduce [in place] + //@return : true/false if n was/wasn't already in the fundamental sector + template + inline bool _11m (Real * const n) { + const bool ret = std::signbit(n[2]); + if(ret) n[2] = -n[2]; + return !ret; + } + + //@brief : reduce a direction to the 222r fundamental sector + //@param n : unit direction to reduce [in place] + //@return : true/false if n was/wasn't already in the fundamental sector + template + inline bool _222r(Real * const n) { + bool ret = _211r(n);//first bring to +z hemisphere with rotation about xy + if(n[1] < n[0]) {//are we below the y==x line + _112(n); + ret = false; + } + return ret; + } + + //@brief : reduce a direction to the mm2r fundamental sector + //@param n : unit direction to reduce [in place] + //@return : true/false if n was/wasn't already in the fundamental sector + template + inline bool mm2r(Real * const n) { + const bool ret = _112(n);//first bring to +y hemisphere with rotation about z + const Real ax = std::fabs(n[0]); + if(ax > n[1]) { + n[0] = std::copysign(n[1], n[0]); + n[1] = ax; + return false; + } + return ret; + } + + //@brief : reduce a direction to the 4 fundamental sector + //@param n : unit direction to reduce [in place] + //@return : true/false if n was/wasn't already in the fundamental sector + template + inline bool _4 (Real * const n) { + const bool ret = _112(n);//first move to +y with 2 fold + if(std::signbit(n[0])) {//need to move to +x with 4 fold + n[0] = -n[0]; + std::swap(n[0], n[1]); + return false; + } + return ret; + } + + //@brief : reduce a direction to the -4 fundamental sector + //@param n : unit direction to reduce [in place] + //@return : true/false if n was/wasn't already in the fundamental sector + template + inline bool b4 (Real * const n) { + bool ret = true; + if(std::signbit(n[2])) {//need to move to +z with -4 + n[0] = -n[0]; + std::swap(n[0], n[1]); + n[2] = -n[2]; + ret = false; + } + if(std::signbit(n[1])) {//need to move to +y with 2 + n[0] = -n[0]; + n[1] = -n[1]; + ret = false; + } + return ret; + } + + //@brief : reduce a direction to the 4mm fundamental sector + //@param n : unit direction to reduce [in place] + //@return : true/false if n was/wasn't already in the fundamental sector + template + inline bool _4mm (Real * const n) { + const bool ret = _4(n);//first move to first quadrant with 4 fold + if(n[1] > n[0]) {//use xy mirror if needed + std::swap(n[0], n[1]); + return false; + } + return ret; + } + + //@brief : reduce a direction to the 3 fundamental sector + //@param n : unit direction to reduce [in place] + //@return : true/false if n was/wasn't already in the fundamental sector + template + inline bool _3 (Real * const n) { + //determine sector + const Real t = std::fabs(n[1] / n[0]);//|tan(theta)| + size_t idx = std::signbit(n[1]) ? 2 : 0;//0, 1, or 2 for first, second, or third sector, initialize with easy cases + if(std::signbit(n[0]) && t <= Constants::r3) idx = 1;//check for second sector + + //apply rotation + switch(idx) { + case 0: return true ;//in first sector, we're done + case 1: rot2d(n, Real(-0.50), -Constants::r3_4); return false;//in second sector, rotate -120 @ z + case 2: rot2d(n, Real(-0.50), Constants::r3_4); return false;//in third sector, rotate 120 @ z + } + throw std::logic_error("unhandled 3 fold case"); + return false; + } + + //@brief : reduce a direction to the 3 fundamental sector rotated 30 degrees about z + //@param n : unit direction to reduce [in place] + //@return : true/false if n was/wasn't already in the fundamental sector + template + inline bool _3r (Real * const n) { + //determine sector + const Real t = std::fabs(n[1] / std::max(std::fabs(n[0]), std::numeric_limits::epsilon()));//|tan(theta)| if in +y hemisphere + size_t idx = (std::signbit(n[0]) ? 1 : 2);//0, 1, or 2 for first, second, or third sector, initialize with easy cases + if(!std::signbit(n[1]) && t >= Real(1) / Constants::r3) idx = 0;//check for first sector + + //apply rotation + switch(idx) { + case 0: return true ;//in first sector, we're done + case 1: rot2d(n, Real(-0.50), -Constants::r3_4); return false;//in second sector, rotate -120 @ z + case 2: rot2d(n, Real(-0.50), Constants::r3_4); return false;//in third sector, rotate 120 @ z + } + throw std::logic_error("unhandled 3 fold case"); + return false; + } + + //@brief : reduce a direction to the 31m fundamental sector + //@param n : unit direction to reduce [in place] + //@return : true/false if n was/wasn't already in the fundamental sector + template + inline bool _31m (Real * const n) { + const bool ret = _3(n);//first reduce to 3 fold fz + const Real t = n[1] / n[0]; + if(n[1] < std::numeric_limits::epsilon() || !(Real(0) <= t && t <= Constants::r3)) {//check if we're above 60 degrees (< eps instead of signbit to handle divide by 0) + mir2d(n, Real(0.5), Constants::r3_4); + return false; + } + return ret; + } + + //@brief : reduce a direction to the 6 fundamental sector + //@param n : unit direction to reduce [in place] + //@return : true/false if n was/wasn't already in the fundamental sector + template + inline bool _6 (Real * const n) { + const bool ret = _112(n);//first bring to +y hemisphere + + //determine sector + const Real t = std::fabs(n[1] / std::max(std::fabs(n[0]), std::numeric_limits::epsilon()));//|tan(theta)| if in +y hemisphere + size_t idx = std::signbit(n[0]) ? 2 : 0; + if(t > Constants::r3) idx = 1;//check for second sector + + //apply rotation + switch(idx) { + case 0: return ret ;//in first sector, we're done + case 1: rot2d(n, Real( 0.50), -Constants::r3_4); return false;//in second sector, rotate -60 @ z + case 2: rot2d(n, Real(-0.50), -Constants::r3_4); return false;//in third sector, rotate -120 @ z + } + throw std::logic_error("unhandled 6 fold case"); + return false; + } + + //@brief : reduce a direction to the 6mm fundamental sector + //@param n : unit direction to reduce [in place] + //@return : true/false if n was/wasn't already in the fundamental sector + template + inline bool _6mm (Real * const n) { + const bool ret = _6(n);//start by reducing to 6 fold fs + + //now check if we're above 30 degrees + const Real t = n[1] / n[0];//tan(theta) + if(t > Real(1) / Constants::r3) {//above 30 degres + mir2d(n, Constants::r3_4, Real(0.5)); + return false; + } + return ret; + } + + //@brief : reduce a direction to the 23 fundamental sector + //@param n : unit direction to reduce [in place] + //@return : true/false if n was/wasn't already in the fundamental sector + template + inline bool _23 (Real * const n) { + bool ret = _211(n);//first bring to +z hemisphere with rotation about y + if(n[1] < n[0]) {//are we below the y==x line + _112(n); + ret = false; + } + + //now handle 3 fold @ 111 + const Real v[5] = {n[0], n[1], -n[0], -n[0], n[2]}; + const size_t idx = std::distance(v, std::max_element(v, v+5));//find maximum potential z + if(n[2] == v[idx]) return ret; + + //negate x/y if needed + if(idx > 1) { + n[0] = -n[0]; + n[1] = -n[1]; + } + + //reshuffle (rotation about 111) and bring back above the y==x line if needed + std::rotate(n, n+1+(idx%2), n+3); + if(n[1] < n[0]) _112(n);//are we below the y==x line + return false; + } + + template inline bool _12m1(Real * const n) {const bool tmp = _121 (n); return _1m1(n) && tmp;}//12/m1 + template inline bool _112m(Real * const n) {const bool tmp = _112 (n); return _11m(n) && tmp;}//112/m + template inline bool _222 (Real * const n) {const bool tmp = _121 (n); return _112(n) && tmp;}//222 + template inline bool mm2 (Real * const n) {const bool tmp = _m11 (n); return _1m1(n) && tmp;}//mm2 + template inline bool mmm (Real * const n) {const bool tmp = mm2 (n); return _11m(n) && tmp;}//mmm + template inline bool mmmr(Real * const n) {const bool tmp = mm2r (n); return _11m(n) && tmp;}//mmmr + template inline bool _4m (Real * const n) {const bool tmp = _4 (n); return _11m(n) && tmp;}//4/m + template inline bool _422 (Real * const n) {const bool tmp = _121 (n); return _4 (n) && tmp;}//422 + template inline bool b42m (Real * const n) {const bool tmp = _121 (n); return mm2r(n) && tmp;}//-42m + template inline bool b4m2 (Real * const n) {const bool tmp = _211r(n); return mm2 (n) && tmp;}//-4m2 + template inline bool _4mmm(Real * const n) {const bool tmp = _11m (n); return _4mm(n) && tmp;}//4/mmm + template inline bool b3 (Real * const n) {const bool tmp = b1 (n); return _3 (n) && tmp;}//-3 + template inline bool _321 (Real * const n) {const bool tmp = _211 (n); return _3 (n) && tmp;}//321 + template inline bool _312 (Real * const n) {const bool tmp = _121 (n); return _3r (n) && tmp;}//312 + template inline bool _3m1 (Real * const n) {const bool tmp = _3r (n); return _m11(n) && tmp;}//3m1 + template inline bool b3m1 (Real * const n) {const bool tmp = _211 (n); return _3m1(n) && tmp;}//-3m1 + template inline bool b31m (Real * const n) {const bool tmp = _121 (n); return _31m(n) && tmp;}//-31m + template inline bool b6 (Real * const n) {const bool tmp = _11m (n); return _3 (n) && tmp;}//-6 + template inline bool _6m (Real * const n) {const bool tmp = _11m (n); return _6 (n) && tmp;}//6/m + template inline bool _622 (Real * const n) {const bool tmp = _121 (n); return _6 (n) && tmp;}//622 + template inline bool b6m2 (Real * const n) {const bool tmp = _121 (n); return _3m1(n) && tmp;}//-6m2 + template inline bool b62m (Real * const n) {const bool tmp = _211 (n); return _31m(n) && tmp;}//-62m + template inline bool _6mmm(Real * const n) {const bool tmp = _11m (n); return _6mm(n) && tmp;}//6/mmm + template inline bool mb3 (Real * const n) {const bool tmp = mmm (n); return r111(n) && tmp;}//m3 + template inline bool _432 (Real * const n) {const bool tmp = _422 (n); return r111(n) && tmp;}//432 + template inline bool b43m (Real * const n) {const bool tmp = _23 (n); return mm2r(n) && tmp;}//-43m + template inline bool mb3m(Real * const n) {const bool tmp = mb3 (n); return _4mm(n) && tmp;}//m3m + } + + //@brief: phenomenologically adjusted hsl2rgb + //@param hsl: hue, saturation, lightness [0,1] (saturation is assumed to be 1) + //@param rgb: location to write rgb [0,1] + //@reference: Nolze, G., & Hielscher, R. (2016). Orientations–perfectly colored. Journal of Applied Crystallography, 49(5), 1786-1802. + template + void sph2rgb(Real const * const hsl, Real * const rgb) { + //get lightness and saturation rescaling parameters + const bool whiteCenter = hsl[2] >= Real(0.5); + const Real yL = whiteCenter ? Real(0.25) : Real(0.50); + const Real yS = whiteCenter ? Real(0.20) : Real(0.50); + + //constants for nonlinear hue adjustment + static const Real iDen = Real( 0.570990316610288181236261564684297686279447800757942106831501845990856895);//1 / ( 1 + sqrt(2*pi) * std::erf( 5 * sqrt(2) / 3 ) * 0.3 ); + static const Real k1 = Real( 0.125331413731550025120788264240552262650349337030496915831496178817114683);// sqrt(pi/2) / 10 + static const Real k2 = Real(14.1421356237309504880168872420969807856967187537694807317667973799073248 );//10 * sqrt(2) + static const Real k1_3 = Real( 0.333333333333333333333333333333333333333333333333333333333333333333333333);// 1/3 + static const Real k1_6 = Real( 0.166666666666666666666666666666666666666666666666666666666666666666666667);// 1/6 + static const Real pi_2 = Real(1.57079632679489661923132169163975144209858469968755291048747229615390820 );// pi/2 + + //adjust hue gradient (A.5) + const Real h3 = std::fmod(hsl[0], k1_3); + const bool half = h3 > k1_6; + const Real h6 = half ? k1_3 - h3 : h3; + const Real hNew = (h6 + k1 * std::erf(k2 * h6)) * iDen; + rgb[0] = hsl[0] - h3 + (half ? k1_3 - hNew : hNew);//save adjusted hue + + //adjust lightness gradient (A.9) + const Real sP = std::sin(hsl[2] * pi_2); + const Real th = yL * hsl[2] + (Real(1) - yL) * sP * sP; + const Real gray = Real(1) - Real(2) * yS * std::fabs(th - Real(0.5)); + rgb[2] = (th - Real(0.5)) * gray + Real(0.5);//save adjusted lightness + + //adjust saturation gradient (A.10) + rgb[1] = gray * ( Real(1) - std::fabs( Real(2) * th - Real(1) ) ) / ( Real(1) - std::fabs( Real(2) * hsl[2] - Real(1) ) );//save adjusted saturation + if(std::isnan(rgb[1])) rgb[1] = Real(0);//correct for divide by 0 + + //convert adjusted hsl to rgb + const Real c = (Real(1) - std::fabs(rgb[2] * 2 - 1)) * rgb[1];//compute chroma + const Real m = rgb[2] - c/2;//m + const Real h = rgb[0] * 6;//hue [0,1] -> [0,6] + const Real x = c * (Real(1) - std::fabs(std::fmod(h, 2) - 1)); + switch((size_t)h) { + case 6://intentional fall through + case 0: rgb[0] = c+m; rgb[1] = x+m; rgb[2] = m; return; + case 1: rgb[0] = x+m; rgb[1] = c+m; rgb[2] = m; return; + case 2: rgb[0] = m; rgb[1] = c+m; rgb[2] = x+m; return; + case 3: rgb[0] = m; rgb[1] = x+m; rgb[2] = c+m; return; + case 4: rgb[0] = x+m; rgb[1] = m; rgb[2] = c+m; return; + case 5: rgb[0] = c+m; rgb[1] = m; rgb[2] = x+m; return; + } + } + + + //////////////////////////////////////////////////////////////////////// + // Spherical Patches // + //////////////////////////////////////////////////////////////////////// + + namespace detail { + namespace detail { + //@brief : 3d vector dot product + //@param v1: first vector + //@param v2: second vector + //@return : dot product + template + Real dot(Real const * const v1, Real const * const v2) { + return std::inner_product(v1, v1+3, v2, Real(0)); + } + + //@brief : 3d vector normalization + //@param v: vector to normalize (in place) + template + void normalize(Real * const v) { + const Real mag = std::sqrt(dot(v, v)); + std::transform(v, v+3, v, [mag](const Real& i){return i/mag;}); + } + + //@brief : 3d vector cross product + //@param v1: first vector + //@param v2: second vector + //@param x : location to write v1 cross v2 + template + void cross(Real const * const v1, Real const * const v2, Real * const x) { + const Real cross[3] = { + x[0] = v1[1] * v2[2] - v1[2] * v2[1], + x[1] = v1[2] * v2[0] - v1[0] * v2[2], + x[2] = v1[0] * v2[1] - v1[1] * v2[0], + }; + std::copy(cross, cross+3, x); + } + } + + //@brief : construct a spherical triangle patch for IPF coloring + //@param nRed : unit direction to color red + //@param nGreen: unit direction to color green + //@param nBlue : unit direction to color blue + //@note : defines triangular patch in CCW order + template + SphericalTriangle::SphericalTriangle(Real const*const nRed, Real const*const nGreen, Real const*const nBlue) { + //copy normals to array for convenience + Real verts[3][3]; + std::copy(nRed , nRed +3, verts[0]); + std::copy(nGreen, nGreen+3, verts[1]); + std::copy(nBlue , nBlue +3, verts[2]); + + //first check if the three points are outside the hemisphere + const Real det = verts[0][0] * verts[1][1] * verts[2][2] + + verts[0][1] * verts[1][2] * verts[2][0] + + verts[0][2] * verts[1][0] * verts[2][1] + - verts[0][0] * verts[1][2] * verts[2][1] + - verts[0][1] * verts[1][0] * verts[2][2] + - verts[0][2] * verts[1][1] * verts[2][0]; + if(det < std::numeric_limits::epsilon()) throw std::runtime_error("spherical triangle must be within single hemisphere"); + + //compute center of spherical triangle (assumes triangle covers less than hemisphere) + Real ctr[3]; + for(size_t i = 0; i < 3; i++) ctr[i] = verts[0][i] + verts[1][i] + verts[2][i]; + detail::normalize(ctr); + + //now build a general spherical patch + SphericalPatch<3, Real>::build(verts, ctr); + }; + + + //@brief : construct a spherical triangle patch for IPF coloring + //@param nGreen: 2D unit direction to color green + //@param nBlue : 2D unit direction to color blue + //@note : red is assumed to be at [0,0,1] + template + SphericalWedge::SphericalWedge(Real const*const nGreen, Real const*const nBlue) { + //copy normals to array for convenience + Real verts[4][3]; + verts[0][0] = verts[0][1] = 0; verts[0][2] = 1; + std::copy(nGreen, nGreen+2, verts[1]); verts[1][2] = 0; + verts[2][0] = verts[2][1] = 0; verts[2][2] =-1; + std::copy(nBlue , nBlue +2, verts[3]); verts[3][2] = 0; + + //compute center + Real ctr[3] = { + nGreen[0] + nBlue[0], + nGreen[1] + nBlue[1], + 0 + }; + + //handle special case of antipodal blue/green + const Real dot = nGreen[0] * nBlue[0] + nGreen[1] * nBlue[1]; + if(std::fabs(dot + Real(1)) < std::numeric_limits::epsilon()) { + ctr[0] = -nGreen[1]; + ctr[1] = nGreen[0]; + } + detail::normalize(ctr); + + //now build a general spherical patch + SphericalPatch<4, Real>::build(verts, ctr); + } + + //@brief : construct a spherical triangle patch to map to the unit hemisphere + //@param verts: vertices of spherical patch (maps to evenly spaced points to equator) + //@param ctr : center of spherical patch (maps to north pole) + //@param filF : fillet fraction, should be 0 for original paper, ~0.05 for perceptually uniform, must be in [0,0.5) + //@note : verts in CCW order + template + void SphericalPatch::build(const Real verts[N][3], const Real ctr[3], const Real filF) { + //save center + std::copy(ctr, ctr+3, center); + + //build orthogonal coordinate system for center -> each vertex + Real vx[N][3], vy[N][3]; + for(size_t i =0; i < N; i++) { + detail::cross(center, verts[i], vy[i]); + detail::cross(vy[i] , center , vx[i]); + detail::normalize(vx[i]); + detail::normalize(vy[i]); + } + std::copy(vx[0], vx[0]+3, rx);//red is the global x direction + std::copy(vy[0], vy[0]+3, ry);//global y is perpindicular to x and patch center (z) + + //compute angles between successive verts + Real angles[N]; + for(size_t i = 0; i < N; i++) angles[i] = std::acos(detail::dot(vx[i], vx[(i+1)%N])); + std::partial_sum(angles, angles+N, cumAngles+1); + + //compute normals of circles defining edges of domain + for(size_t i = 0; i < N; i++) { + detail::cross(verts[i], verts[(i+1)%N], normals[i]); + detail::normalize(normals[i]); + } + + //compute cutoff angles for filleting + Real deltas[N]; + std::fill(deltas, deltas + N, Constants::pi2 / N);//for now just evenly space deltas + for(size_t i = 0; i < N; i++) {//loop over edges + for(size_t j = 0; j < 3; j++) {//loop over verts + const Real delta = 2 == j ? 0 : std::copysign(filF * deltas[i], 0 == j ? 1 : -1); + cutoffs[i*3+j] = cumAngles[ i +(j==0 ? 0 : 1)] + delta; + } + } + + //numerically compute r and dr/dtheta at transition points between linear and filleted regions + Real radii[2*N], dRadii[2*N]; + for(size_t i = 0; i < N; i++) {//loop over edges + //compute angles where radius needs to be calculated + const Real dT (0.1);//angular offset from vertex -> transition points (for numerical derivative calculation), ~1/2 degree + const Real hf(0.01);//fractional distance numerical derivative calculation (should probably be <= 1) + const Real thetas[6] = { + cutoffs[3*i+0] - hf * dT,//symmetric points for derivative calculation + cutoffs[3*i+0] ,//first transition + cutoffs[3*i+0] + hf * dT,//symmetric points for derivative calculation + cutoffs[3*i+1] - hf * dT,//symmetric points for derivative calculation + cutoffs[3*i+1] ,//second transition + cutoffs[3*i+1] + hf * dT //symmetric points for derivative calculation + }; + + //apply rotations and compute radius/angle at each point + Real r[6]; + for(size_t j = 0; j < 6; j++) { + //compute normal of circle at desired angle (ry rotated about center) + const Real c = std::cos(thetas[j] / 2); + const Real s = std::sin(thetas[j] / 2); + + //q * n (w == 0 since rotation axis is perpendicular to vector) + const Real x = c * ry[0] + s * (center[1] * ry[2] - center[2] * ry[1]); + const Real y = c * ry[1] + s * (center[2] * ry[0] - center[0] * ry[2]); + const Real z = c * ry[2] + s * (center[0] * ry[1] - center[1] * ry[0]); + + const Real m[3] = {//q * n * q.conj() [normal of circle at desired angle] + x * c + s * (z * center[1] - y * center[2]), + y * c + s * (x * center[2] - z * center[0]), + z * c + s * (y * center[0] - x * center[1]), + }; + + //now compute intersection of two unit circles at origin w/ normals v and normals[edge] + const Real& nx = normals[i][0]; + const Real& ny = normals[i][1]; + const Real& nz = normals[i][2]; + const Real& mx = m[0]; + const Real& my = m[1]; + const Real& mz = m[2]; + const Real den = std::sqrt( nx * nx * ( my * my + mz * mz ) + ny * ny * ( mz * mz + mx * mx ) + nz * nz * ( mx * mx + my * my ) - Real(2) * ( nz * nx * mz * mx + nx * ny * mx * my + ny * nz * my * mz ) ); + + Real v[3] = {//intersection of two circles (point along edge i at angle thetas[j]) + (ny * mz - nz * my) / den, + (nz * mx - nx * mz) / den, + (nx * my - ny * mx) / den, + }; + if(std::signbit(detail::dot(v, center))) std::transform(v, v+3, v, std::negate());//select intersection point closest to center + r[j] = std::acos(detail::dot(v, center));//compute angle from center -> edge at this theta + } + + //save radii and compute derivative + radii [i*2+0] = r[1]; + radii [i*2+1] = r[4]; + dRadii[i*2+0] = (r[2] - r[0]) / (hf * dT * 2); + dRadii[i*2+1] = (r[5] - r[3]) / (hf * dT * 2); + } + + //compute polynomial coefficients to remove discontinuity in r + for(size_t i = 0; i < N; i++) {//loop over edge + const size_t j = (i+1)%N;//get index of next edge + const Real v1 = radii [i*2+1];//value of radius at transition point in edge i (near edge j) + const Real v2 = radii [j*2+0];//value of radius at transition point in edge j (near edge i) + const Real m1 = dRadii[i*2+1] * filF * angles[i];//value of d(radius)/d(theta) at transition point (multiply by range of -1->0 to correct derivative scaling) + const Real m2 = dRadii[j*2+0] * filF * angles[j];//value of d(radius)/d(theta) at transition point (multiply by range of 0->1 to correct derivative scaling) + coeffs[i][0] = ( m1 + m2 + v1 - v2 ) / 4; + coeffs[i][1] = (-m1 + m2 ) / 4; + coeffs[i][2] = (-m1 - m2 - v1 * 3 + v2 * 3) / 4; + coeffs[i][3] = ( m1 - m2 + v1 * 2 + v2 * 2) / 4; + } + + //build lookup table for nonlinear hue adjustment (to keep hue at vert i i/N) + //this is a numerical lookup table to solve A.6 + + //compute the fractional angle of each vertex + Real rhoN[N]; + for(size_t i = 1; i < N; i++) { + Real v[3]; + std::transform(verts[i], verts[i] + 3, center, v, std::minus()); + Real angle = std::atan2(detail::dot(ry, v), detail::dot(rx, v)); + if(std::signbit(angle)) angle+= Constants::pi2; + rhoN[i-1] = angle / Constants::pi2;//convert angle w.r.t. red --> fractional + } + rhoN[N-1] = 1; + + //create evenly spaced list for angle from 0->1 + omega.resize(256 * N);//~256 point between successive verts + std::vector irho(omega.size()); + irho.resize(omega.size()); + std::iota(irho.begin(), irho.end(), Real(0)); + std::for_each(irho.begin(), irho.end(), [&](Real&i){i /= Real(irho.size() - 1);}); + + //compute the distance to the sector edge at each angle (in irho) + omega[0] = 0; + for(size_t i = 0; i < omega.size() - 1; i++) { + //create vector normal to center at angle irho[i] + Real n[3]; + Real s = std::sin(Constants::pi2 * irho[i]); + Real c = std::cos(Constants::pi2 * irho[i]); + std::transform(rx, rx+3, ry, n, [s, c](Real i, Real j){return i * s - j * c;}); + + //determine which edge is closest and compute distance to edge + Real normxn[3]; + const size_t j = std::distance(rhoN, std::upper_bound(rhoN, rhoN+N, irho[i]));//which edge is closest + detail::cross(normals[j], n, normxn); + const Real mag = std::sqrt(detail::dot(normxn, normxn)); + omega[i+1] = std::acos(detail::dot(normxn, center) / mag); + } + + //get the offset to the vertices + size_t idx[N+1] = {0}; + for(size_t i = 0; i < N; i++) idx[i+1] = std::distance(irho.begin(), std::upper_bound(irho.begin(), irho.end(), rhoN[i])); + + //normalize + for(size_t i = 0; i < N; i++) { + const Real sum = std::accumulate(omega.begin() + idx[i], omega.begin() + idx[i+1], Real(0)) * N;//we want the distance from vert i -> i+1 to cover 1/N + std::for_each (omega.begin() + idx[i], omega.begin() + idx[i+1], [sum](Real& r){r /= sum;}); + } + + //integrate + std::partial_sum(omega.begin(), omega.end(), omega.begin());//now we have our lookup table + } + + //@brief : convert a unit direction inside the patch to fractional polar coordinates on the hemisphere + //@param n : unit direction to color + //@param tht: fractional azimuthal angle [0,1] maps to [0,2*pi] + //@param phi: fractional polar angle [0,1] maps to [0,pi/2] + template + void SphericalPatch::toHemi(Real const * const n, Real& tht, Real& phi) const { + //compute angle with red direction + Real v[3]; + const Real n0[3] = {n[0], n[1], n[2]};//in case input and output overlap + std::transform(n0, n0 + 3, center, v, std::minus()); + Real angle = std::atan2(detail::dot(ry, v), detail::dot(rx, v)); + if(std::signbit(angle)) angle+= Constants::pi2; + tht = angle / Constants::pi2;//convert angle w.r.t. red --> fractional + + //apply adaptive hue gradient + tht *= omega.size() - 1;//rescale from [0,1] to lookup table size + const size_t iOmg = (size_t)tht;//get index of lower bound in lookup table + if(iOmg+1 < omega.size()) {//don't go out of bounds if tht == 1 + tht = omega[iOmg] + (omega[iOmg+1] - omega[iOmg]) * ((iOmg+1) - tht);//linearly interpolate from lookup table + } else { + tht = Real(1); + } + + //compute polar angle + const size_t idx = std::distance(cutoffs, std::lower_bound(cutoffs, cutoffs + 3 * N, angle));//determine which region this angle falls in + const size_t i = idx / 3;//index of edge + phi = std::acos(detail::dot(n0, center));//angle between center and point + if(phi < std::numeric_limits::epsilon()) return;//avoid divide by zero issues + + //normalize polar angle + switch(idx - i * 3) { + case 1: {//in linear region, normalize angle by max possible angle + Real nxc[3]; + detail::cross(n0 , center, nxc);//normal of arc through n/center + detail::cross(normals[i], nxc , v );//intersection of two circles (edge of patch in direction tht) + detail::normalize(v); + phi /= std::acos(detail::dot(v, center)) * Real(2);//compute fractional progress ot edge + } break; + + case 0: {//in first fillet + const size_t j = (i+N-1)%N;//get i-1 with periodic boundary conditions + Real x = (angle - cumAngles[i ]) / (cutoffs[idx ] - cumAngles[i ]); + const Real den = coeffs[j][0] * x * x * x + coeffs[j][1] * x * x + coeffs[j][2] * x + coeffs[j][3]; + phi /= std::max(phi, den) * Real(2);//normalize, clipping at 1/2 + } break; + + case 2: {//in second fillet + Real x = -(angle - cumAngles[i+1]) / (cutoffs[idx-1] - cumAngles[i+1]); + const Real den = coeffs[i][0] * x * x * x + coeffs[i][1] * x * x + coeffs[i][2] * x + coeffs[i][3]; + phi /= std::max(phi, den) * Real(2);//normalize, clipping at 1/2 + } break; + } + } + + //@brief : compute coloring for a unit direction in the fundamental sector + //@param n : unit direction in fundamental sector (undefined behavior for directions outside of sector) + //@param rgb: location to write color [0,1] + //@param h2r: hsl2rgb like coloring function to use with h, s, and l in [0,1] and output as [0,1] rgb: void(Real const * const hsl, Real * const rgb) + //@param wCn: white/black center (north/south hemisphere) + //@param nTh: should theta be negated (to create enatiomorph / reverse color progression) + template + void SphericalPatch::toColor(Real const * const n, Real * const rgb, std::function h2r, const bool wCn, const bool nTh) const { + toHemi(n, rgb[0], rgb[2]); + rgb[1] = Real(1);//fully satruated + if(nTh) rgb[0] = Real(1) - rgb[0]; + if(wCn) rgb[2] = Real(1) - rgb[2]; + h2r(rgb, rgb); + } + } +} + +#endif//_sphere_sector_h_ diff --git a/include/xtal/symmetry.hpp b/include/xtal/symmetry.hpp new file mode 100644 index 0000000..64c5e14 --- /dev/null +++ b/include/xtal/symmetry.hpp @@ -0,0 +1,2429 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _symmetry_h_ +#define _symmetry_h_ + +#include +#include +#include + +namespace xtal { + + //@brief: a class to abstract point group operations + //@note : I've opted for an enumeration type approach over polymorphism since there are finite crystallographic point groups + // This choice has benefits and drawbacks and either option can work + // I choose an enumeration style primarily to make the class easier to instantiate and to prevent the potential for confusion from pointer casting + // now that there is a general position class a point group could also be defined as a vector of general positions but that seems a bit cumbersome + struct PointGroup { + //////////////////////////////////////////////////////////////////////// + // Constructors / Basic Attribute Queries // + //////////////////////////////////////////////////////////////////////// + + //@brief : construct a point group from a space group + //@param sg: space group number [1,230] + //@param as: should the alternate setting 2 group be used (e.g. P 1 1 2 instead of P 1 2 1 for group 3) + //@note : alternate setting is only supported for monoclinic groups [3,15] + PointGroup(const uint_fast8_t sg = 1, const bool as = false); + + //@brief : construct a point group from a string + //@param pg: name of point group (e.g. m3m) + //@note : setting can be selected by using the full name e.g. "121" or "112" instead of just "2" + //@note : laue group and schonflies names can also be used + PointGroup(std::string pg); + + //@brief : a factory method to produce rotated point groups + //@param pg: name of point group (e.g. 222r for 2.22) + //@return : special rotated point group + //@note : these exist for e.g. the rotational group of -4m2, only use them if you know what you're doing + static PointGroup BuildOrtho45(std::string pg); + + //@brief : get a list of acceptable names to construct a point group from + //@return: list of acceptable names + static std::vector Names(); + + //@brief : get the point group number in IUCr order (1-32) + //@return: point group number + uint_fast8_t number() const; + + //@brief : determine if there are multiple axis choices for this point group (e.g. 112 vs 121 or -4m2 vs -42m) + //@return: true/false for multiple/single axis choice(s) + bool hasMultAxis() const; + + //@brief : get the short Hermann-Mauguin name for the point group + //@param lng: true/false to get a longer unambiguous name for multi setting groups (e.g. 1m1 or 11m instead of just m) + //@return : the name of the point group (e.g. "4/mmm") + //@note : rotoinversion axis are denoted as -n instead of \bar{n} + std::string name(const bool lng = true) const; + + //@brief : get the full Hermann-Mauguin name for the point group + //@param lng: true/false to get a longer unambiguous name for multi setting groups (e.g. 1m1 or 11m instead of just m) + //@return : the name of the point group (e.g. "\frac{4}{m}\frac{2}{m}\frac{2}{m}") + //@note : formatted for latex typesetting + std::string fullName(const bool lng = true) const; + + //@brief : get the Schonflies name for the point group + //@param alt: true/false to get the alternate symbol (e.g. S2 instead of Ci for -1) + //@return : the name of the point group (e.g. "D4h") + //@note : all characters after the first are subscripts + std::string schonflies(const bool alt = false) const; + + //@brief : get the Groth (1921) name for the point group + //@param alt: true/false to get the alternate symbol (e.g. S2 instead of Ci for -1) + //@return : the name of the point group (e.g. "Ditetragonal-dipyramidal") + //@note : all characters after the first are subscripts + std::string groth() const; + + //@brief : get the Friedel (1926) name for the point group + //@param alt: true/false to get the alternate symbol (e.g. S2 instead of Ci for -1) + //@return : the name of the point group (e.g. "Holohedry") + //@note : all characters after the first are subscripts + std::string friedel() const; + + //@brief : get the TSL 'numbering' for this point group + //@return: TSL number + uint_fast8_t tslNum() const; + + //@brief : get the HKL 'numbering' for this point group (laue group number [1,11]) + //@return: HKL number + uint_fast8_t hklNum() const; + + //@brief : construct a point group from a TSL number + //@param tsl: TSL Laue group number (e.g. 62) + static PointGroup FromTSL(const uint_fast8_t tsl); + + //@brief : construct a point group from a HKL number + //@param hkl: HKL Laue group number (e.g. 62) + static PointGroup FromHKL(const uint_fast8_t hkl); + + //@brief : get the order of the group + //@return: order + uint_fast8_t order() const; + + //////////////////////////////////////////////////////////////////////// + // Point Group Relationships // + //////////////////////////////////////////////////////////////////////// + + //@brief : get the name of the laue group this point group belongs to + //@return: name of laue group e.g. "CubicLow" + std::string laueName() const; + + //@brief : get the laue group the point group belongs to + //@return: point group for the the laue group this point group belongs to + PointGroup laueGroup() const; + + //@brief : get the purely rotational group the point group belongs to + //@return: point group for the the purely rotational group this point group belongs to + PointGroup rotationGroup() const; + + //@brief : get the symmorphic space group of this point group + //@param lat: lattice type (must be one of p, c, a, f, i, or r) + //@return : space group number (or 0 if this point group doesn't have a space group for the choosen lattice type) + uint_fast8_t symmorphic(const char lat = 'p') const; + + //@brief : get the transformation matrix from the default symmorphic setting to this space group + //@return : 3x3 transformation matrix A such that A^T * M * A is the symmetry operation m in the new reference frame (or null if in standard frame) + //@note : returns 45@z for 222r type groups, 90@x for 112 type groups, null otherwise + template Real const * symmorphicTrns() const; + + //////////////////////////////////////////////////////////////////////// + // Symmetry Attributes // + //////////////////////////////////////////////////////////////////////// + + //@brief : check if this point group has inversion symmetry + //@return: true/false if this point group does/doesn't have an inversino center + //@note : true/false also corresponds to centrosymmetric/non-centrosymmetric + bool inversion() const; + + //@brief : check if this point group has enantiomorphism + //@return: true/false if this crystal does / doesn't have enantiomorphism + //@note : true means no mirror planes or inversion symmetry + bool enantiomorphism() const; + + //@brief : check if this point group has a mirror plane perpendicular to the z axis + //@return: true/false if there is/isn't a mirror perpendicular to the z axis + bool zMirror() const; + + //@brief : check if this point group has a zRot() planes with normals in the equator (e.g. Nmm where N == zRot() ) + //@return: 0 - there are no mirrors with normals in the equatorial plane + // : 1 - there are are mirrors with normals in the equatorial plane with normal alignment (e.g. 31m and -4m2) + // : 2 - there are are mirrors with normals in the equatorial plane with rotated alignment (e.g. 31m and -42m) + uint_fast8_t mmType() const; + + //@brief : get rotational symmetry about z axis + //@return: degree of rotational symmetry about z + uint_fast8_t zRot() const; + + //@brief : get the number of rotational symmetry operators + //@return: number of rotational symmetry operators + uint_fast8_t numRotOps() const; + + //@brief : get the closed set of rotational symmetry operators + //@return: pointer to rotational symmetry operators (as w,x,y,z quaternions) + template Real const * rotOps() const; + + //@brief : get the number of mirror planes + //@return: number of mirror planes + uint_fast8_t numMirror() const; + + //@brief : get mirror plane normals + //@return: pointer to list of mirror plane normals (as x,y,z unit vectors) + template Real const * mirrors() const; + + //@brief : get the number of rotational symmetry operators + //@return: number of rotational symmetry operators + uint_fast8_t numRotAxis() const; + + //@brief : get the rotational symmetry axis + //@return: pointer to rotational symmetry axis (as n,x,y,z where xyz is a unit axis and n is the order (negative for rotoinversion)) + template Real const * rotAxis() const; + + //////////////////////////////////////////////////////////////////////// + // Symmetry Operations // + //////////////////////////////////////////////////////////////////////// + + //@brief : check if a rodrigues vector is in the fundamental zone + //@param ro: orientation to check (x, y, z, tan(w/2)) + //@return : true/false if ro is/isn't in the fundamental zone + template bool roInFz(Real const * const ro) const; + + //@brief : compute the symmetric equivalent orientation in the fundamental zone + //@param qu: orientation to compute symmetric equivalent of + //@param fz: location to write symmetric equivalent + template void fzQu(Real const * const qu, Real * const fz) const; + + //@brief : compute the disorientation between 2 orientations + //@param qu1: first orientation to compute disorientation of (as w,x,y,z quaternion) + //@param qu2: second orientation to compute disorientation of (as w,x,y,z quaternion) + //@param dis: location to write disorientation (as w,x,y,z quaternion) + //@note : qu2 is assumed to belong to the same point group + template void disoQu(Real const * const qu1, Real const * const qu2, Real * const dis) const; + + //@brief: compute the symmetric equivalent orientation of qu2 closest to qu1 + //@param qu1: orientation to search near + //@param qu2: orientation to compute symmetric equivalent of + //@param qu3: location to write symmetric equivalent of qu2 closest to qu1 + template void nearbyQu(Real const * const qu1, Real const * const qu2, Real * const equiv) const; + + //@brief : reduce a unit direction to the fundamental sector + //@param n : unit direction to reduce (magnitude is assumed to be 1) + //@param fs: location to write symmetric equivalent of n in the fundamental sector (can be the same as n) + //@return : true if n was already in the fundamental sector + template bool fsDir(Real const * const n, Real * const fs) const; + + //@param n : crystal direction to color to color (magnitude is assumed to be 1) + //@param rgb: location to write color [0,1] + //@param h2r: hsl2rgb like coloring function to use with h, s, and l in [0,1] and output as [0,1] rgb: void(Real const * const hsl, Real * const rgb) + template void ipfColor(Real const * const n, Real * const rgb, std::function h2r) const; + + //@brief : check if two point groups are the same + //@param rhs: other point group to compare against + //@return : true if rhs is the same point group, false otherwise + bool operator==(const PointGroup& rhs) const {return type == rhs.type;} + + //@brief : check if two point groups are different + //@param rhs: other point group to compare against + //@return : false if rhs is the same point group, true otherwise + bool operator!=(const PointGroup& rhs) const {return type != rhs.type;} + + //@brief : comparison (for sorting) + //@param rhs: other point group to compare against + //@return : true if this points groups type is higher in the enumeration the ths + bool operator<(const PointGroup& rhs) const {return type < rhs.type;} + + private: + enum class PG { + //triclinic + _1,// 1 "1" + _b1,// \bar{1} "-1" + + //monoclinic (b unique preferred) + _121,// 121 "121" (b unique) + _112,// 112 "112" (c unique) + _1m1,// 1m1 "1m1" (b unique) + _11m,// 11m "11m" (c unique) + _12m1,// 12/m1 "12/m1" (b unique) + _112m,// 112/m "112/m" (c unique) + + //orthorhombic + _222,// 222 "222" + _222r,// 222 rotated 45 degrees (2 fold axis at z, xy, and -xy) [this is 2.22 in the international tables] {see 10.1.3 for details} + _mm2,// mm2 "mm2" + _mm2r,// mm2 rotated 45 degrees (as 222r, this is a maximal subgroup of -42m where -4m2 has mm2) [this would be m.m2 in the international tables] + _mmm,// mmm "mmm" + _mmmr,// mmm rotated 45 degrees (laue group of _222r) [this would be m.mm in the international tables] + + //tetragonal + _4,// 4 "4" + _b4,// \bar{4} "-4" + _4m,// 4/m "4/m" + _422,// 422 "422" + _4mm,// 4mm "4mm" + _b42m,// \bar{4}2m "-42m" (2m vs m2) + _b4m2,// \bar{4}m2 "-4m2" (2m vs m2) + _4mmm,// 4/mmm "4/mmm" + + //trigonal + _3,// 3 "3" + _b3,// \bar{3} "-3" + _321,// 321 "321" (x secondary) + _312,// 312 "312" (y secondary) + _3m1,// 3m1 "3m1" (x secondary) + _31m,// 31m "31m" (y secondary) + _b3m1,// \bar{3}m1 "-3m1" (x secondary) + _b31m,// \bar{3}1m "-31m" (y secondary) + + //hexagonal + _6,// 6 "6" + _b6,// \bar{6} "-6" + _6m,// 6/m "6/m" + _622,// 622 "622" + _6mm,// 6mm "6mm" + _b6m2,// \bar{6}m2 "-6m2" (2m vs m2) + _b62m,// \bar{6}2m "-62m" (2m vs m2) + _6mmm,// 6/mmm "6/mmm" + + //cubic + _23,// 23 "23" + _mb3,// m3 "m3" + _432,// 432 "432" + _b43m,// \bar{4}3m "-43m" + _mb3m,// m\bar{3}m "m3m" + }; + + PG type;//poing group type + + //@brief : construct a point group from a point group enumeration + //@param pg: point group enumeration + PointGroup(const PG& pg) : type(pg) {} + + //////////////////////////////////////////////////////////////////////// + // Static Functions for Symmetry Specific Code // + //////////////////////////////////////////////////////////////////////// + + //@brief : check if an orientation is in the fundamental zone + //@param ro: orientation to check as [x, y, z, tan(w/2)] + //@return : true/false if the orientation is/isn't in the fundamental zone + //@note : these are specializations for cyclic, dihedral, and cubic symmetries + template static bool FZ1 (Real const * const ro) {return ro[3] >= 0;}//negative tan(theta/2) -> outside of [0,pi] (negate all)} + template static bool FZ121 (Real const * const ro); + template static bool FZ112 (Real const * const ro); + template static bool FZ222 (Real const * const ro); + template static bool FZ222r(Real const * const ro); + template static bool FZ3 (Real const * const ro); + template static bool FZ321 (Real const * const ro); + template static bool FZ312 (Real const * const ro); + template static bool FZ4 (Real const * const ro); + template static bool FZ422 (Real const * const ro); + template static bool FZ6 (Real const * const ro); + template static bool FZ622 (Real const * const ro); + template static bool FZ23 (Real const * const ro);// 32 + template static bool FZ432 (Real const * const ro);// 432 + + //@brief : test x and y against 60 degree ro fz cutting plane + //@param ro: orientation to check as [x, y, z, tan(w/2)] + //@param yx: true to swap x and y + //@return : true if inside fundamental zone + template static bool FZ3xy (Real const * const ro, const bool yx); + + //@brief : compute the disorientation between 2 orientations + //@param qu1: first orientation to compute disorientation of + //@param qu2: second orientation to compute disorientation of + //@param dis: location to write disorientation + //@param op1: rotational symmetry operators as quaternions (wxyz) for the first orientation + //@param no1: number of rotational symmetry operators for the first orientation + //@param op2: rotational symmetry operators as quaternions (wxyz) for the second orientation + //@param no2: number of rotational symmetry operators for the second orientation + template static void DisoQu(Real const * const qu1, Real const * const qu2, Real * const dis, Real const * const op1, const uint_fast8_t no1, Real const * const op2, const uint_fast8_t no2); + + //@brief : compute the disorientation between 2 cubic (432) orientations + //@param qu1: first orientation to compute disorientation of + //@param qu2: second orientation to compute disorientation of + //@param dis: location to write disorientation + //@note : this shortcut is significantly faster than the general algorithm + template static void Diso432(Real const * const qu1, Real const * const qu2, Real * const dis); + }; +} + +//////////////////////////////////////////////////////////////////////// +// Implementations // +//////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include "quaternion.hpp" +#include "rotations.hpp" +#include "sphere_sector.hpp" +#include "constants.hpp" + +namespace xtal { + + //////////////////////////////////////////////////////////////////////// + // Constructors / Basic Attribute Queries // + //////////////////////////////////////////////////////////////////////// + + //@brief : construct a point group from a space group + //@param sg: space group number [1,230] + //@param as: should the alternate setting 2 group be used (e.g. P 1 1 2 instead of P 1 2 1 for group 3) + //@note : alternate setting is only supported for monoclinic groups [3,15] + PointGroup::PointGroup(const uint_fast8_t sg, const bool as) { + //look up table to convert from a space group to a point group + static const PG SG2PG[230] = { + //triclinic + PG:: _1, PG:: _b1, // 1- 2 + + //monoclinic + PG:: _121, PG:: _121, PG:: _121, // 3- 5 + PG:: _1m1, PG:: _1m1, PG:: _1m1, PG:: _1m1, // 6- 9 + PG::_12m1, PG::_12m1, PG::_12m1, PG::_12m1, PG::_12m1, PG::_12m1,// 10- 15 + + //orthorhombic + PG:: _222, PG:: _222, PG:: _222, PG:: _222, PG:: _222, PG:: _222,// 16- 24 + PG:: _222, PG:: _222, PG:: _222, + PG:: _mm2, PG:: _mm2, PG:: _mm2, PG:: _mm2, PG:: _mm2, PG:: _mm2,// 25- 46 + PG:: _mm2, PG:: _mm2, PG:: _mm2, PG:: _mm2, PG:: _mm2, PG:: _mm2, + PG:: _mm2, PG:: _mm2, PG:: _mm2, PG:: _mm2, PG:: _mm2, PG:: _mm2, + PG:: _mm2, PG:: _mm2, PG:: _mm2, PG:: _mm2, + PG:: _mmm, PG:: _mmm, PG:: _mmm, PG:: _mmm, PG:: _mmm, PG:: _mmm,// 47- 74 + PG:: _mmm, PG:: _mmm, PG:: _mmm, PG:: _mmm, PG:: _mmm, PG:: _mmm, + PG:: _mmm, PG:: _mmm, PG:: _mmm, PG:: _mmm, PG:: _mmm, PG:: _mmm, + PG:: _mmm, PG:: _mmm, PG:: _mmm, PG:: _mmm, PG:: _mmm, PG:: _mmm, + PG:: _mmm, PG:: _mmm, PG:: _mmm, PG:: _mmm, + + //tetragonal + PG:: _4, PG:: _4, PG:: _4, PG:: _4, PG:: _4, PG:: _4,// 75- 80 + PG:: _b4, PG:: _b4, // 81- 82 + PG:: _4m, PG:: _4m, PG:: _4m, PG:: _4m, PG:: _4m, PG:: _4m,// 83- 88 + PG:: _422, PG:: _422, PG:: _422, PG:: _422, PG:: _422, PG:: _422,// 89- 98 + PG:: _422, PG:: _422, PG:: _422, PG:: _422, + PG:: _4mm, PG:: _4mm, PG:: _4mm, PG:: _4mm, PG:: _4mm, PG:: _4mm,// 99-110 + PG:: _4mm, PG:: _4mm, PG:: _4mm, PG:: _4mm, PG:: _4mm, PG:: _4mm, + PG::_b42m, PG::_b42m, PG::_b42m, PG::_b42m, //111-114 + PG::_b4m2, PG::_b4m2, PG::_b4m2, PG::_b4m2, PG::_b4m2, PG::_b4m2,//115-120 + PG::_b42m, PG::_b42m, //121-122 + PG::_4mmm, PG::_4mmm, PG::_4mmm, PG::_4mmm, PG::_4mmm, PG::_4mmm,//123-142 + PG::_4mmm, PG::_4mmm, PG::_4mmm, PG::_4mmm, PG::_4mmm, PG::_4mmm, + PG::_4mmm, PG::_4mmm, PG::_4mmm, PG::_4mmm, PG::_4mmm, PG::_4mmm, + PG::_4mmm, PG::_4mmm, + + //trigonal + PG:: _3, PG:: _3, PG:: _3, PG:: _3, //143-146 + PG:: _b3, PG:: _b3, //147-148 + PG:: _312, PG:: _321, PG:: _312, PG:: _321, PG:: _312, PG:: _321,//149-155 + PG:: _321, + PG:: _3m1, PG:: _31m, PG:: _3m1, PG:: _31m, PG:: _3m1, PG:: _3m1,//156-161 + PG::_b31m, PG::_b31m, PG::_b3m1, PG::_b3m1, PG::_b3m1, PG::_b3m1,//162-167 + + //hexagonal + PG:: _6, PG:: _6, PG:: _6, PG:: _6, PG:: _6, PG:: _6,//168-173 + PG:: _b6, //174 + PG:: _6m, PG:: _6m, //175-176 + PG:: _622, PG:: _622, PG:: _622, PG:: _622, PG:: _622, PG:: _622,//177-182 + PG:: _6mm, PG:: _6mm, PG:: _6mm, PG:: _6mm, //183-186 + PG::_b6m2, PG::_b6m2, PG::_b62m, PG::_b62m, //187-190 + PG::_6mmm, PG::_6mmm, PG::_6mmm, PG::_6mmm, //191-194 + + //cubic + PG:: _23, PG:: _23, PG:: _23, PG:: _23, PG:: _23, //195-199 + PG:: _mb3, PG:: _mb3, PG:: _mb3, PG:: _mb3, PG:: _mb3, PG:: _mb3,//200-206 + PG:: _mb3, + PG:: _432, PG:: _432, PG:: _432, PG:: _432, PG:: _432, PG:: _432,//207-214 + PG:: _432, PG:: _432, + PG::_b43m, PG::_b43m, PG::_b43m, PG::_b43m, PG::_b43m, PG::_b43m,//215-220 + PG::_mb3m, PG::_mb3m, PG::_mb3m, PG::_mb3m, PG::_mb3m, PG::_mb3m,//221-230 + PG::_mb3m, PG::_mb3m, PG::_mb3m, PG::_mb3m + }; + + if(sg < 1 || sg > 230) throw std::runtime_error("point group number must be [1,230]"); + type = SG2PG[sg-1]; + } + + //@brief : construct a point group from a string + //@param pg: name of point group (e.g. m3m) + //@note : setting can be selected by using the full name e.g. "121" or "112" instead of just "2" + //@note : laue group and schonflies names can also be used + PointGroup::PointGroup(std::string pg) { + //first make a lowercase copy of the point group name with all white space removed + pg.erase(remove_if(pg.begin(), pg.end(), ::isspace), pg.end());//remove white space + std::transform(pg.begin(), pg.end(), pg.begin(), ::tolower);//convert to lower case + + //now make a list of all possible point groups + //note that _222r, _mm2r, and _mmmr are excluded since they should be for internal use only + // 222r can be constructed as PointGroup("-4m2").rotationGroup(); + // mmmr can be constructed as PointGroup("-4m2").rotationGroup().laueGroup(); + // mm2r can't currently be constructed publicly + const PG groups[40] = { + PG:: _1, PG:: _b1, PG:: _121, PG:: _112, PG:: _1m1, PG:: _11m, + PG::_12m1, PG::_112m, PG:: _222, PG:: _mm2, PG:: _mmm, PG:: _4, + PG:: _b4, PG:: _4m, PG:: _422, PG:: _4mm, PG::_b42m, PG::_b4m2, + PG::_4mmm, PG:: _3, PG:: _b3, PG:: _312, PG:: _321, PG:: _31m, + PG:: _3m1, PG::_b31m, PG::_b3m1, PG:: _6, PG:: _b6, PG:: _6m, + PG:: _622, PG:: _6mm, PG::_b6m2, PG::_b62m, PG::_6mmm, PG:: _23, + PG:: _mb3, PG:: _432, PG::_b43m, PG::_mb3m + }; + + //Schonflies start with a capital letter + std::string pgUp = pg; + if(!pg.empty()) pgUp.front() = ::toupper(pg.front()); + + //loop over point groups checking names + for(size_t i = 0; i < 40; i++) { + PointGroup g(groups[i]); + bool match = false; + if (0 == pg.compare(g.name(true ) )) match = true;//unambigous HM name + else if(0 == pg.compare(g.name(false) )) match = true;//ambigous HM name (e.g. "2" instead of "121") + else { + if(0 == pgUp.compare(g.schonflies())) match = true;//schonflies name + } + if(match) { + type = g.type; + return; + } + } + + //if a point group didn't work, try laue group names + if (0 == pg.compare("triclinic" )) {type = PG:: _b1; return;} + else if(0 == pg.compare("monoclinic" )) {type = PG::_12m1; return;} + else if(0 == pg.compare("orthorhombic" )) {type = PG:: _mmm; return;} + else if(0 == pg.compare("trigonallow" )) {type = PG:: _b3; return;} + else if(0 == pg.compare("trigonalhigh" )) {type = PG::_b3m1; return;} + else if(0 == pg.compare("trigonal" )) {type = PG::_b3m1; return;}//assume high symmetry if not specified + else if(0 == pg.compare("tetragonallow" )) {type = PG:: _4m; return;} + else if(0 == pg.compare("tetragonalhigh")) {type = PG::_4mmm; return;} + else if(0 == pg.compare("tetragonal" )) {type = PG::_4mmm; return;}//assume high symmetry if not specified + else if(0 == pg.compare("hexagonallow" )) {type = PG:: _6m; return;} + else if(0 == pg.compare("hexagonalhigh" )) {type = PG::_6mmm; return;} + else if(0 == pg.compare("hexagonal" )) {type = PG::_6mmm; return;}//assume high symmetry if not specified + else if(0 == pg.compare("cubiclow" )) {type = PG:: _mb3; return;} + else if(0 == pg.compare("cubichigh" )) {type = PG::_mb3m; return;} + else if(0 == pg.compare("cubic" )) {type = PG::_mb3m; return;}//assume high symmetry if not specified + + //if we didn't find a match the string isn't valid + throw std::runtime_error("couldn't parse a point group type from '" + pg + "'"); + } + + //@brief : a factory method to produce rotated point groups + //@param pg: name of point group (e.g. 222r for 2.22) + //@return : special rotated point group + //@note : these exist for e.g. the rotational group of -4m2, only use them if you know what you're doing + PointGroup PointGroup::BuildOrtho45(std::string pg) { + if ("222r" == pg) return PointGroup(PG::_222r); + else if("mmmr" == pg) return PointGroup(PG::_mmmr); + else if("mm2r" == pg) return PointGroup(PG::_mm2r); + else throw std::runtime_error("invalid name for 45 degree rotated orthorhombic group '" + pg + "'"); + } + + + //@brief : get a list of acceptable names to construct a point group from + //@return: list of acceptable names + std::vector PointGroup::Names() { + //make a list of all possible point groups + const PG groups[40] = { + PG:: _1, PG:: _b1, PG:: _121, PG:: _112, PG:: _1m1, PG:: _11m, + PG::_12m1, PG::_112m, PG:: _222, PG:: _mm2, PG:: _mmm, PG:: _4, + PG:: _b4, PG:: _4m, PG:: _422, PG:: _4mm, PG::_b42m, PG::_b4m2, + PG::_4mmm, PG:: _3, PG:: _b3, PG:: _312, PG:: _321, PG:: _31m, + PG:: _3m1, PG::_b31m, PG::_b3m1, PG:: _6, PG:: _b6, PG:: _6m, + PG:: _622, PG:: _6mm, PG::_b6m2, PG::_b62m, PG::_6mmm, PG:: _23, + PG:: _mb3, PG:: _432, PG::_b43m, PG::_mb3m + }; + + //accumulate HM names + std::vector names; + for(size_t i = 0; i < 40; i++) { + PointGroup g(groups[i]); + names.push_back(g.name(true)); + const std::string nm = g.name(false);//get ambigous name as well (e.g. "32" instead of "321") + if(0 != names.back().compare(nm)) names.push_back(nm);//add ambigous name if it is different + } + + //accmulate schonflies names + for(size_t i = 0; i < 40; i++) { + PointGroup g(groups[i]); + const std::string nm = g.schonflies(); + if(0 != names.back().compare(nm)) names.push_back(nm); + } + + //accumulate laue group names + names.push_back("Triclinic" );// -1 + names.push_back("Monoclinic" );// 2/m + names.push_back("Orthorhombic" );// mmm + names.push_back("TrigonalLow" );// -3 + names.push_back("TrigonalHigh" );// -3m + names.push_back("Trigonal" );// -3m + names.push_back("TetragonalLow" );// 4/m + names.push_back("TetragonalHigh");// 4/mmm + names.push_back("Tetragonal" );// 4/mmm + names.push_back("HexagonalLow" );// 6/m + names.push_back("HexagonalHigh" );// 6/mmm + names.push_back("Hexagonal" );// 6/mmm + names.push_back("CubicLow" );// m3 + names.push_back("CubicHigh" );// m3m + names.push_back("Cubic" );// m3m + return names; + } + + //@brief : get the point group number in IUCr order (1-32) + //@return: point group number + uint_fast8_t PointGroup::number() const { + switch(type) { + case PG:: _1: return 1; + case PG:: _b1: return 2; + case PG:: _121://intentional fall through + case PG:: _112: return 3; + case PG:: _1m1://intentional fall through + case PG:: _11m: return 4; + case PG::_12m1://intentional fall through + case PG::_112m: return 5; + case PG:: _222://intentional fall through + case PG::_222r: return 6; + case PG:: _mm2://intentional fall through + case PG::_mm2r: return 7; + case PG:: _mmm://intentional fall through + case PG::_mmmr: return 8; + case PG:: _4: return 9; + case PG:: _b4: return 10; + case PG:: _4m: return 11; + case PG:: _422: return 12; + case PG:: _4mm: return 13; + case PG::_b42m://intentional fall through + case PG::_b4m2: return 14; + case PG::_4mmm: return 15; + case PG:: _3: return 16; + case PG:: _b3: return 17; + case PG:: _321://intentional fall through + case PG:: _312: return 18; + case PG:: _3m1://intentional fall through + case PG:: _31m: return 19; + case PG::_b3m1://intentional fall through + case PG::_b31m: return 20; + case PG:: _6: return 21; + case PG:: _b6: return 22; + case PG:: _6m: return 23; + case PG:: _622: return 24; + case PG:: _6mm: return 25; + case PG::_b62m://intentional fall through + case PG::_b6m2: return 26; + case PG::_6mmm: return 27; + case PG:: _23: return 28; + case PG:: _mb3: return 29; + case PG:: _432: return 30; + case PG::_b43m: return 31; + case PG::_mb3m: return 32; + } + return -1; + } + + //@brief : determine if there are multiple axis choices for this point group (e.g. 112 vs 121 or -4m2 vs -42m) + //@return: true/false for multiple/single axis choice(s) + bool PointGroup::hasMultAxis() const { + switch(type) { + case PG:: _121: + case PG:: _112: + case PG:: _1m1: + case PG:: _11m: + case PG::_12m1: + case PG::_112m: + case PG::_b42m: + case PG::_b4m2: + case PG:: _312: + case PG:: _321: + case PG:: _31m: + case PG:: _3m1: + case PG::_b31m: + case PG::_b3m1: + case PG::_b6m2: + case PG::_b62m: return true; + + case PG:: _1: + case PG:: _b1: + case PG:: _222://these could be considered as multiple conventions + case PG::_222r://but 222r is extremely unusual (and shouldn't be used) + case PG:: _mm2://these could be considered as multiple conventions + case PG::_mm2r://but mm2r is extremely unusual (and shouldn't be used) + case PG:: _mmm://these could be considered as multiple conventions + case PG::_mmmr://but mmmr is extremely unusual (and shouldn't be used) + case PG:: _4: + case PG:: _b4: + case PG:: _4m: + case PG:: _422: + case PG:: _4mm: + case PG:: _3: + case PG:: _b3: + case PG:: _6: + case PG:: _b6: + case PG:: _6m: + case PG:: _622: + case PG:: _6mm: + case PG::_4mmm: + case PG::_6mmm: + case PG:: _23: + case PG:: _mb3: + case PG:: _432: + case PG::_b43m: + case PG::_mb3m: return false; + } + return false; + } + + //@brief : get the short Hermann-Mauguin name for the point group + //@param lng: true/false to get a longer unambiguous name for multi setting groups (e.g. 1m1 or 11m instead of just m) + //@return : the name of the point group (e.g. "4/mmm") + //@note : rotoinversion axis are denoted as -n instead of \bar{n} + std::string PointGroup::name(const bool lng) const { + switch(type) { + case PG:: _1: return "1"; + case PG:: _b1: return "-1"; + case PG:: _121: return lng ? "121" : "2"; + case PG:: _112: return "112";//always use full name for non default ambigous + case PG:: _1m1: return lng ? "1m1" : "m"; + case PG:: _11m: return "11m";//always use full name for non default ambigous + case PG::_12m1: return lng ? "12/m1" : "2/m"; + case PG::_112m: return "112/m";//always use full name for non default ambigous + case PG:: _222: return "222"; + case PG::_222r: return "222r"; + case PG:: _mm2: return "mm2"; + case PG::_mm2r: return "mm2r"; + case PG:: _mmm: return "mmm"; + case PG::_mmmr: return "mmmr"; + case PG:: _4: return "4"; + case PG:: _b4: return "-4"; + case PG:: _4m: return "4/m"; + case PG:: _422: return "422"; + case PG:: _4mm: return "4mm"; + case PG::_b42m: return "-42m"; + case PG::_b4m2: return "-4m2"; + case PG::_4mmm: return "4/mmm"; + case PG:: _3: return "3"; + case PG:: _b3: return "-3"; + case PG:: _321: return lng ? "321" : "32"; + case PG:: _312: return "312";//always use full name for non default ambigous + case PG:: _3m1: return lng ? "3m1" : "3m"; + case PG:: _31m: return "31m";//always use full name for non default ambigous + case PG::_b3m1: return lng ? "-3m1" : "-3m"; + case PG::_b31m: return "-31m";//always use full name for non default ambigous + case PG:: _6: return "6"; + case PG:: _b6: return "-6"; + case PG:: _6m: return "6/m"; + case PG:: _622: return "622"; + case PG:: _6mm: return "6mm"; + case PG::_b62m: return "-62m"; + case PG::_b6m2: return "-6m2"; + case PG::_6mmm: return "6/mmm"; + case PG:: _23: return "23"; + case PG:: _mb3: return "m3"; + case PG:: _432: return "432"; + case PG::_b43m: return "-43m"; + case PG::_mb3m: return "m3m"; + } + return ""; + } + + //@brief : get the full Hermann-Mauguin name for the point group + //@param lng: true/false to get a longer unambiguous name for multi setting groups (e.g. 1m1 or 11m instead of just m) + //@return : the name of the point group (e.g. "\frac{4}{m}\frac{2}{m}\frac{2}{m}") + //@note : formatted for latex typesetting + std::string PointGroup::fullName(const bool lng) const { + switch(type) { + case PG:: _1: return "1" ; + case PG:: _b1: return "\\bar{1}" ; + case PG:: _121: return lng ? "121" : "2" ; + case PG:: _112: return "112" ;//always use full name for non default ambigous + case PG:: _1m1: return lng ? "1m1" : "m" ; + case PG:: _11m: return "11m" ;//always use full name for non default ambigous + case PG::_12m1: return lng ? "1\\frac{2}{m}1" : "\\frac{2}{m}" ; + case PG::_112m: return "11\\frac{2}{m}" ;//always use full name for non default ambigous + case PG:: _222: return "222" ; + case PG::_222r: return "222r" ; + case PG:: _mm2: return "mm2" ; + case PG::_mm2r: return "mm2r" ; + case PG:: _mmm: return "\\frac{2}{m}\\frac{2}{m}\\frac{2}{m}" ; + case PG::_mmmr: return "\\frac{2}{m}\\frac{2}{m}\\frac{2}{m}r" ; + case PG:: _4: return "4" ; + case PG:: _b4: return "\\bar{4}" ; + case PG:: _4m: return "\\frac{4}{m}" ; + case PG:: _422: return "422" ; + case PG:: _4mm: return "4mm" ; + case PG::_b42m: return "\\bar{4}2m" ; + case PG::_b4m2: return "\\bar{4}m2" ; + case PG::_4mmm: return "\\frac{4}{m}\\frac{2}{m}\\frac{2}{m}" ; + case PG:: _3: return "3" ; + case PG:: _b3: return "\\bar{3}" ; + case PG:: _321: return lng ? "321" : "32" ; + case PG:: _312: return "312" ;//always use full name for non default ambigous + case PG:: _3m1: return lng ? "3m1" : "3m" ; + case PG:: _31m: return "31m" ;//always use full name for non default ambigous + case PG::_b3m1: return lng ? "\\bar{3}\\frac{2}{m}1" : "\\bar{3}\\frac{2}{m}"; + case PG::_b31m: return "\\bar{3}1\\frac{2}{m}" ;//always use full name for non default ambigous + case PG:: _6: return "6" ; + case PG:: _b6: return "\\bar{6}" ; + case PG:: _6m: return "\\frac{6}{m}" ; + case PG:: _622: return "622" ; + case PG:: _6mm: return "6mm" ; + case PG::_b62m: return "\\bar{6}2m" ; + case PG::_b6m2: return "\\bar{6}m2" ; + case PG::_6mmm: return "\\frac{6}{m}\\frac{2}{m}\\frac{2}{m}" ; + case PG:: _23: return "23" ; + case PG:: _mb3: return "\\frac{2}{m}\\bar{3}" ; + case PG:: _432: return "432" ; + case PG::_b43m: return "\\bar{4}3m" ; + case PG::_mb3m: return "\\frac{4}{m}\\bar{3}\\frac{2}{m}" ; + } + return ""; + } + + //@brief : get the Schonflies name for the point group + //@param alt: true/false to get the alternate symbol (e.g. S2 instead of Ci for -1) + //@return : the name of the point group (e.g. "D4h") + //@note : all characters after the first are subscripts + std::string PointGroup::schonflies(const bool alt) const { + switch(type) { + case PG:: _1: return "C1" ; + case PG:: _b1: return alt ? "S2" : "Ci" ; + case PG:: _121: + case PG:: _112: return "C2" ; + case PG:: _1m1: + case PG:: _11m: return alt ? "C1h" : "Cs" ; + case PG::_12m1: + case PG::_112m: return "C2h"; + case PG:: _222: + case PG::_222r: return alt ? "V" : "D2" ; + case PG:: _mm2: + case PG::_mm2r: return "C2v"; + case PG:: _mmm: + case PG::_mmmr: return alt ? "Vh" : "D2h"; + case PG:: _4: return "C4" ; + case PG:: _b4: return "S4" ; + case PG:: _4m: return "C4h"; + case PG:: _422: return "D4" ; + case PG:: _4mm: return "C4v"; + case PG::_b42m: + case PG::_b4m2: return alt ? "Vd" : "D2d"; + case PG::_4mmm: return "D4h"; + case PG:: _3: return "C3" ; + case PG:: _b3: return alt ? "S6" : "C3i"; + case PG:: _321: + case PG:: _312: return "D3" ; + case PG:: _3m1: + case PG:: _31m: return "C3v"; + case PG::_b3m1: + case PG::_b31m: return "D3d"; + case PG:: _6: return "C6" ; + case PG:: _b6: return "C3h"; + case PG:: _6m: return "C6h"; + case PG:: _622: return "D6" ; + case PG:: _6mm: return "C6v"; + case PG::_b62m: + case PG::_b6m2: return "D3h"; + case PG::_6mmm: return "D6h"; + case PG:: _23: return "T" ; + case PG:: _mb3: return "Th" ; + case PG:: _432: return "O" ; + case PG::_b43m: return "Td" ; + case PG::_mb3m: return "Oh" ; + } + return ""; + } + + //@brief : get the Groth (1921) name for the point group + //@param alt: true/false to get the alternate symbol (e.g. S2 instead of Ci for -1) + //@return : the name of the point group (e.g. "Ditetragonal-dipyramidal") + //@note : all characters after the first are subscripts + std::string PointGroup::groth() const { + switch(type) { + case PG:: _1: return "pedial (asymmetric)" ; + case PG:: _b1: return "pinacoidal" ; + case PG:: _121: + case PG:: _112: return "sphenoidal" ; + case PG:: _1m1: + case PG:: _11m: return "domatic" ; + case PG::_12m1: + case PG::_112m: return "prismatic" ; + case PG:: _222: + case PG::_222r: return "disphenoidal" ; + case PG:: _mm2: + case PG::_mm2r: return "pyramidal" ; + case PG:: _mmm: + case PG::_mmmr: return "dipyramidal" ; + case PG:: _4: return "pryamidal" ; + case PG:: _b4: return "disphenoidal" ; + case PG:: _4m: return "dipyramidal" ; + case PG:: _422: return "trapezoihedral" ; + case PG:: _4mm: return "ditetragonal-pyramidal" ; + case PG::_b42m: + case PG::_b4m2: return "scalenohedral" ; + case PG::_4mmm: return "ditetragonal-dipyramidal" ; + case PG:: _3: return "pyramidal" ; + case PG:: _b3: return "rhombohedral" ; + case PG:: _321: + case PG:: _312: return "trapezohedral" ; + case PG:: _3m1: + case PG:: _31m: return "ditrigonal-pyramidal" ; + case PG::_b3m1: + case PG::_b31m: return "ditrigonal-scalenohedral" ; + case PG:: _6: return "pyramidal" ; + case PG:: _b6: return "trigonal-dipyramidal" ; + case PG:: _6m: return "dipyramidal" ; + case PG:: _622: return "trapezohedral" ; + case PG:: _6mm: return "dihexagonal-pyramidal" ; + case PG::_b62m: + case PG::_b6m2: return "ditrigonal-dipyramidal" ; + case PG::_6mmm: return "dihexagonal-dipyramidal" ; + case PG:: _23: return "tetrahedral-pentagondodecahedral (tetartoidal)"; + case PG:: _mb3: return "disdodecahderal (diploidal)" ; + case PG:: _432: return "pentagon-icositetrahedral (gyroidal)" ; + case PG::_b43m: return "hexakistetrahedral (hextetrahedral)" ; + case PG::_mb3m: return "hexakisoctahedral (hexoctahedral)" ; + } + return ""; + } + + //@brief : get the Friedel (1926) name for the point group + //@param alt: true/false to get the alternate symbol (e.g. S2 instead of Ci for -1) + //@return : the name of the point group (e.g. "Holohedry") + //@note : all characters after the first are subscripts + std::string PointGroup::friedel() const { + switch(type) { + case PG:: _1: return "hemihedry" ; + case PG:: _b1: return "holohedry" ; + case PG:: _121: + case PG:: _112: return "holoaxial hemihedry" ; + case PG:: _1m1: + case PG:: _11m: return "antihemihedry" ; + case PG::_12m1: + case PG::_112m: return "holohedry" ; + case PG:: _222: + case PG::_222r: return "holoaxial hemihedry" ; + case PG:: _mm2: + case PG::_mm2r: return "antihemihedry" ; + case PG:: _mmm: + case PG::_mmmr: return "holohedry" ; + case PG:: _4: return "tetartohedry with 4-axis" ; + case PG:: _b4: return "sphenohedral tartohedry" ; + case PG:: _4m: return "parahemihedry" ; + case PG:: _422: return "holoaxial hemihedry" ; + case PG:: _4mm: return "antihemihedry with 4-axis" ; + case PG::_b42m: + case PG::_b4m2: return "sphenohderal antihemihedry" ; + case PG::_4mmm: return "holohedry" ; + case PG:: _3: return "ogdohedry" ;//rhombohedral: tetartohedry + case PG:: _b3: return "paratetartohedry" ;//rhombohedral: parahemihedry + case PG:: _321: + case PG:: _312: return "holoaxial tetartohedry with 3-axis";//rhombohedral: holoaxial hemihedry + case PG:: _3m1: + case PG:: _31m: return "hemimorphic antitetartohedry" ;//rhombohedral: antihemihedry + case PG::_b3m1: + case PG::_b31m: return "parahemihedry with 3-axis" ;//rhombohedral: holohedry + case PG:: _6: return "tetartohedry with 6-axis" ; + case PG:: _b6: return "trigonohedral antitetartohedry" ; + case PG:: _6m: return "parahemihedry with 6-axis" ; + case PG:: _622: return "holoaxial hemihedry" ; + case PG:: _6mm: return "antihemihedry with 6-axis" ; + case PG::_b62m: + case PG::_b6m2: return "trigonohedral antihemihedry" ; + case PG::_6mmm: return "holohedry" ; + case PG:: _23: return "tetartohedry" ; + case PG:: _mb3: return "parahemihedry" ; + case PG:: _432: return "holoaxial hemihedry" ; + case PG::_b43m: return "antihemihedry" ; + case PG::_mb3m: return "holohedry" ; + } + return ""; + } + + //@brief : get the TSL 'numbering' for this point group + //@return: TSL number + uint_fast8_t PointGroup::tslNum() const { + switch(laueGroup().type) { + case PG:: _b1: return 1; + case PG::_12m1: + case PG::_112m: return 2; + case PG:: _mmm: + case PG::_mmmr: return 22; + case PG:: _4m: return 4; + case PG::_4mmm: return 42; + case PG:: _b3: return 3; + case PG::_b3m1: + case PG::_b31m: return 32; + case PG:: _6m: return 6; + case PG::_6mmm: return 62; + case PG:: _mb3: return 23; + case PG::_mb3m: return 43; + default : throw std::logic_error("laueGroup() '" + laueGroup().name() + "' not handled for tslNum()"); + } + } + + //@brief : get the HKL 'numbering' for this point group (laue group number [1,11]) + //@return: HKL number + uint_fast8_t PointGroup::hklNum() const { + switch(laueGroup().type) { + case PG:: _b1: return 1; + case PG::_12m1: + case PG::_112m: return 2; + case PG:: _mmm: + case PG::_mmmr: return 3; + case PG:: _4m: return 4; + case PG::_4mmm: return 5; + case PG:: _b3: return 6; + case PG::_b3m1: + case PG::_b31m: return 7; + case PG:: _6m: return 8; + case PG::_6mmm: return 9; + case PG:: _mb3: return 10; + case PG::_mb3m: return 11; + default : throw std::logic_error("laueGroup() '" + laueGroup().name() + "' not handled for hklNum()"); + } + } + + //@brief : construct a point group from a TSL number + //@param tsl: TSL Laue group number (e.g. 62) + PointGroup PointGroup::FromTSL(const uint_fast8_t tsl) { + switch(tsl) { + case 1: return PointGroup( "-1"); + case 2: return PointGroup( "2/m"); + case 22: return PointGroup( "mmm"); + case 4: return PointGroup( "4/m"); + case 42: return PointGroup("4/mmm"); + case 3: return PointGroup( "-3"); + case 32: return PointGroup( "-3m"); + case 6: return PointGroup( "6/m"); + case 62: return PointGroup("6/mmm"); + case 23: return PointGroup( "m3"); + case 43: return PointGroup( "m3m"); + default: throw std::domain_error("couldn't get point group from TSL number"); + } + } + + //@brief : construct a point group from a HKL number + //@param hkl: HKL Laue group number (e.g. 11) + PointGroup PointGroup::FromHKL(const uint_fast8_t hkl) { + switch(hkl) { + case 1: return PointGroup( "-1"); + case 2: return PointGroup( "2/m"); + case 3: return PointGroup( "mmm"); + case 4: return PointGroup( "4/m"); + case 5: return PointGroup("4/mmm"); + case 6: return PointGroup( "-3"); + case 7: return PointGroup( "-3m"); + case 8: return PointGroup( "6/m"); + case 9: return PointGroup("6/mmm"); + case 10: return PointGroup( "m3"); + case 11: return PointGroup( "m3m"); + default: throw std::domain_error("couldn't get point group from HKL number"); + } + } + + //@brief : get the order of the group + //@return: order + uint_fast8_t PointGroup::order() const { + switch(type) { + case PG:: _1: return 1; + case PG:: _b1: + case PG:: _121: + case PG:: _112: + case PG:: _1m1: + case PG:: _11m: return 2; + case PG::_12m1: + case PG::_112m: + case PG:: _222: + case PG::_222r: + case PG:: _mm2: + case PG::_mm2r: + case PG:: _4: + case PG:: _b4: return 4; + case PG:: _mmm: + case PG::_mmmr: + case PG:: _4m: + case PG:: _422: + case PG:: _4mm: + case PG::_b42m: + case PG::_b4m2: return 8; + case PG::_4mmm: return 16; + case PG:: _3: return 3; + case PG:: _b3: + case PG:: _321: + case PG:: _312: + case PG:: _3m1: + case PG:: _31m: + case PG:: _6: + case PG:: _b6: return 6; + case PG::_b3m1: + case PG::_b31m: + case PG:: _6m: + case PG:: _622: + case PG:: _6mm: + case PG::_b62m: + case PG::_b6m2: + case PG:: _23: return 12; + case PG::_6mmm: + case PG:: _mb3: + case PG:: _432: + case PG::_b43m: return 24; + case PG::_mb3m: return 48; + } + return 0; + } + + //////////////////////////////////////////////////////////////////////// + // Point Group Relationships // + //////////////////////////////////////////////////////////////////////// + + //@brief : get the name of the laue group this point group belongs to + //@return: name of laue group e.g. "CubicLow" + std::string PointGroup::laueName() const { + switch(laueGroup().type) { + case PG:: _b1: return "Triclinic" ; + case PG::_12m1: + case PG::_112m: return "Monoclinic" ; + case PG:: _mmm: + case PG::_mmmr: return "Orthorhombic" ; + case PG:: _4m: return "TetragonalLow"; + case PG::_4mmm: return "Tetragonal" ; + case PG:: _b3: return "TrigonalLow" ; + case PG::_b3m1: + case PG::_b31m: return "Trigonal" ; + case PG:: _6m: return "HexagonalLow" ; + case PG::_6mmm: return "Hexagonal" ; + case PG:: _mb3: return "CubicLow" ; + case PG::_mb3m: return "Cubic" ; + default : throw std::logic_error("laueGroup() '" + laueGroup().name() + "' not handled for laueName()"); + } + } + + //@brief : get the laue group the point group belongs to + //@return: point group for the the laue group this point group belongs to + PointGroup PointGroup::laueGroup() const { + switch(type) { + case PG:: _1: + case PG:: _b1: return PointGroup(PG:: _b1); + case PG:: _121: + case PG:: _1m1: + case PG::_12m1: return PointGroup(PG::_12m1); + case PG:: _112: + case PG:: _11m: + case PG::_112m: return PointGroup(PG::_112m); + case PG:: _222: + case PG:: _mm2: + case PG:: _mmm: return PointGroup(PG:: _mmm); + case PG::_222r: + case PG::_mm2r: + case PG::_mmmr: return PointGroup(PG::_mmmr); + case PG:: _4: + case PG:: _b4: + case PG:: _4m: return PointGroup(PG:: _4m); + case PG:: _422: + case PG:: _4mm: + case PG::_b42m: + case PG::_b4m2: + case PG::_4mmm: return PointGroup(PG::_4mmm); + case PG:: _3: + case PG:: _b3: return PointGroup(PG:: _b3); + case PG:: _321: + case PG:: _3m1: + case PG::_b3m1: return PointGroup(PG::_b3m1); + case PG:: _312: + case PG:: _31m: + case PG::_b31m: return PointGroup(PG::_b31m); + case PG:: _6: + case PG:: _b6: + case PG:: _6m: return PointGroup(PG:: _6m); + case PG:: _622: + case PG:: _6mm: + case PG::_b6m2: + case PG::_b62m: + case PG::_6mmm: return PointGroup(PG::_6mmm); + case PG:: _23: + case PG:: _mb3: return PointGroup(PG:: _mb3); + case PG:: _432: + case PG::_b43m: + case PG::_mb3m: return PointGroup(PG::_mb3m); + } + return PointGroup(PG::_b1); + } + + //@brief : get the purely rotational group the point group belongs to + //@return: point group for the the purely rotational group this point group belongs to + PointGroup PointGroup::rotationGroup() const { + switch(type) { + case PG:: _1: + case PG:: _b1: + case PG:: _1m1: + case PG:: _11m: return PointGroup(PG:: _1); + case PG:: _121: + case PG::_12m1: return PointGroup(PG:: _121); + case PG:: _112: + case PG::_112m: + case PG:: _mm2: + case PG::_mm2r: + case PG:: _b4: return PointGroup(PG:: _112); + case PG:: _222: + case PG:: _mmm: + case PG::_b42m: return PointGroup(PG:: _222); + case PG::_222r: + case PG::_mmmr: + case PG::_b4m2: return PointGroup(PG::_222r);//222 rotated 45 degrees + case PG:: _4: + case PG:: _4m: + case PG:: _4mm: return PointGroup(PG:: _4); + case PG:: _422: + case PG::_4mmm: return PointGroup(PG:: _422); + case PG:: _3: + case PG:: _b3: + case PG:: _3m1: + case PG:: _31m: + case PG:: _b6: return PointGroup(PG:: _3); + case PG:: _321: + case PG::_b3m1: + case PG::_b62m: return PointGroup(PG:: _321); + case PG:: _312: + case PG::_b31m: + case PG::_b6m2: return PointGroup(PG:: _312); + case PG:: _6: + case PG:: _6m: + case PG:: _6mm: return PointGroup(PG:: _6); + case PG:: _622: + case PG::_6mmm: return PointGroup(PG:: _622); + case PG:: _23: + case PG:: _mb3: + case PG::_b43m: return PointGroup(PG:: _23); + case PG:: _432: + case PG::_mb3m: return PointGroup(PG:: _432); + } + return PointGroup(PG::_1); + } + + //@brief : get the symmorphic space group of this point group + //@param lat: lattice type (must be one of p, c, a, f, i, or r) + //@return : space group number (or 0 if this point group doesn't have a space group for the choosen lattice type) + uint_fast8_t PointGroup::symmorphic(const char lat) const { + const char x = std::toupper(lat);// e.g. 'p' -> 'P' + switch(type) { + case PG:: _1: return 'P' == x ? 1 : 0; + case PG:: _b1: return 'P' == x ? 2 : 0; + + //monoclinic b (alternate cell choices currently commented out) + case PG:: _121: return 'P' == x ? 3 : ( ('C' == x /*|| 'A' == x || 'I' == x*/) ? 5 : 0); + case PG:: _1m1: return 'P' == x ? 6 : ( ('C' == x /*|| 'A' == x || 'I' == x*/) ? 8 : 0); + case PG::_12m1: return 'P' == x ? 10 : ( ('C' == x /*|| 'A' == x || 'I' == x*/) ? 12 : 0); + + //monoclinic c (alternate cell choices currently commented out) + case PG:: _112: return 'P' == x ? 3 : ( ('A' == x /*|| 'B' == x || 'I' == x*/) ? 5 : 0); + case PG:: _11m: return 'P' == x ? 6 : ( ('A' == x /*|| 'B' == x || 'I' == x*/) ? 8 : 0); + case PG::_112m: return 'P' == x ? 10 : ( ('A' == x /*|| 'B' == x || 'I' == x*/) ? 12 : 0); + + //orthorhombic (currently only abc setting) + case PG::_222: return 'P' == x ? 16 : ( 'C' == x ? 21 : ( 'F' == x ? 22 : ( 'I' == x ? 23 : 0 ) ) ); + case PG::_mm2: return 'P' == x ? 25 : ( 'C' == x ? 35 : ( 'F' == x ? 42 : ( 'I' == x ? 44 : ( 'A' == x ? 38 : 0 ) ) ) ); + case PG::_mmm: return 'P' == x ? 47 : ( 'C' == x ? 65 : ( 'F' == x ? 69 : ( 'I' == x ? 71 : 0 ) ) ); + + //rotated orthorhombic + case PG::_222r: return 'P' == x ? 16 : ( 'C' == x ? 21 : ( 'F' == x ? 22 : ( 'I' == x ? 23 : 0 ) ) ); + case PG::_mm2r: return 'P' == x ? 25 : ( 'C' == x ? 35 : ( 'F' == x ? 42 : ( 'I' == x ? 44 : ( 'A' == x ? 38 : 0 ) ) ) ); + case PG::_mmmr: return 'P' == x ? 47 : ( 'C' == x ? 65 : ( 'F' == x ? 69 : ( 'I' == x ? 71 : 0 ) ) ); + + //tetragonal + case PG:: _4: return 'P' == x ? 75 : ('I' == x ? 79 : 0); + case PG:: _b4: return 'P' == x ? 81 : ('I' == x ? 82 : 0); + case PG:: _4m: return 'P' == x ? 83 : ('I' == x ? 87 : 0); + case PG:: _422: return 'P' == x ? 89 : ('I' == x ? 97 : 0); + case PG:: _4mm: return 'P' == x ? 99 : ('I' == x ? 107 : 0); + case PG::_b42m: return 'P' == x ? 111 : ('I' == x ? 121 : 0); + case PG::_b4m2: return 'P' == x ? 115 : ('I' == x ? 119 : 0); + case PG::_4mmm: return 'P' == x ? 123 : ('I' == x ? 139 : 0); + + //trigonal + case PG:: _3: return 'P' == x ? 143 : ('R' == x ? 146 : 0); + case PG:: _b3: return 'P' == x ? 147 : ('R' == x ? 148 : 0); + case PG:: _321: return 'P' == x ? 150 : ('R' == x ? 155 : 0); + case PG:: _312: return 'P' == x ? 149 : ('R' == x ? 155 : 0); + case PG:: _3m1: return 'P' == x ? 156 : ('R' == x ? 160 : 0); + case PG:: _31m: return 'P' == x ? 157 : ('R' == x ? 160 : 0); + case PG::_b3m1: return 'P' == x ? 164 : ('R' == x ? 166 : 0); + case PG::_b31m: return 'P' == x ? 162 : ('R' == x ? 166 : 0); + + //hexagonal + case PG:: _6: return 'P' == x ? 168 : 0; + case PG:: _b6: return 'P' == x ? 174 : 0; + case PG:: _6m: return 'P' == x ? 175 : 0; + case PG:: _622: return 'P' == x ? 177 : 0; + case PG:: _6mm: return 'P' == x ? 183 : 0; + case PG::_b6m2: return 'P' == x ? 187 : 0; + case PG::_b62m: return 'P' == x ? 189 : 0; + case PG::_6mmm: return 'P' == x ? 191 : 0; + + //cubic + case PG:: _23: return 'P' == x ? 195 : ('F' == x ? 196 : ('I' == x ? 197 : 0 ) ) ; + case PG:: _mb3: return 'P' == x ? 200 : ('F' == x ? 202 : ('I' == x ? 204 : 0 ) ) ; + case PG:: _432: return 'P' == x ? 207 : ('F' == x ? 209 : ('I' == x ? 211 : 0 ) ) ; + case PG::_b43m: return 'P' == x ? 215 : ('F' == x ? 216 : ('I' == x ? 217 : 0 ) ) ; + case PG::_mb3m: return 'P' == x ? 221 : ('F' == x ? 225 : ('I' == x ? 229 : 0 ) ) ; + + default: return 0; + } + } + + //@brief : get the transformation matrix from the default symmorphic setting to this space group + //@return : 3x3 transformation matrix A such that A^T * M * A is the symmetry operation m in the new reference frame (or null if in standard frame) + //@note : returns 45@z for 222r type groups, 90@x for 112 type groups, null otherwise + template + Real const * PointGroup::symmorphicTrns() const { + static const Real monoC[9] = {1, 0, 0, 0, 0, -1, 0, 1, 1};//90@x + static const Real orthR[9] = {//45@z + Constants::r1_2, -Constants::r1_2, 0, + Constants::r1_2, Constants::r1_2, 0, + 0, 0, 1 + }; + + switch(type) { + case PG:: _1: + case PG:: _b1: + case PG:: _121: + case PG:: _1m1: + case PG::_12m1: + case PG:: _222: + case PG:: _mm2: + case PG:: _mmm: + case PG:: _4: + case PG:: _b4: + case PG:: _4m: + case PG:: _422: + case PG:: _4mm: + case PG::_b42m: + case PG::_b4m2: + case PG::_4mmm: + case PG:: _3: + case PG:: _b3: + case PG:: _321: + case PG:: _312: + case PG:: _3m1: + case PG:: _31m: + case PG::_b3m1: + case PG::_b31m: + case PG:: _6: + case PG:: _b6: + case PG:: _6m: + case PG:: _622: + case PG:: _6mm: + case PG::_b6m2: + case PG::_b62m: + case PG::_6mmm: + case PG:: _23: + case PG:: _mb3: + case PG:: _432: + case PG::_b43m: + case PG::_mb3m: return NULL; + + case PG:: _112: + case PG:: _11m: + case PG::_112m: return monoC; + + case PG::_222r: + case PG::_mm2r: + case PG::_mmmr: return orthR; + } + return NULL; + } + + //////////////////////////////////////////////////////////////////////// + // Symmetry Attributes // + //////////////////////////////////////////////////////////////////////// + + //@brief : check if this point group has inversion symmetry + //@return: true/false if this point group does/doesn't have an inversino center + bool PointGroup::inversion() const { + switch(type) { + case PG:: _1: + case PG:: _121: + case PG:: _112: + case PG:: _1m1: + case PG:: _11m: + case PG:: _222: + case PG::_222r: + case PG:: _mm2: + case PG::_mm2r: + case PG:: _4: + case PG:: _b4: + case PG:: _422: + case PG:: _4mm: + case PG::_b42m: + case PG::_b4m2: + case PG:: _3: + case PG:: _321: + case PG:: _312: + case PG:: _3m1: + case PG:: _31m: + case PG:: _6: + case PG:: _b6: + case PG:: _622: + case PG:: _6mm: + case PG::_b6m2: + case PG::_b62m: + case PG:: _23: + case PG:: _432: + case PG::_b43m: return false; + + case PG:: _b1: + case PG::_12m1: + case PG::_112m: + case PG:: _mmm: + case PG::_mmmr: + case PG:: _4m: + case PG::_4mmm: + case PG:: _b3: + case PG::_b3m1: + case PG::_b31m: + case PG:: _6m: + case PG::_6mmm: + case PG:: _mb3: + case PG::_mb3m: return true ; + } + return false; + } + + //@brief : check if this point group has enantiomorphism + //@return: true/false if this crystal does / doesn't have enantiomorphism + //@note : true means no mirror planes or inversion symmetry + bool PointGroup::enantiomorphism() const { + switch(type) { + case PG:: _1: + case PG:: _121: + case PG:: _112: + case PG:: _222: + case PG::_222r: + case PG:: _4: + case PG:: _422: + case PG:: _3: + case PG:: _321: + case PG:: _312: + case PG:: _6: + case PG:: _622: + case PG:: _23: + case PG:: _432: return true; + + case PG:: _b1: + case PG:: _1m1: + case PG:: _11m: + case PG::_12m1: + case PG::_112m: + case PG:: _mm2: + case PG::_mm2r: + case PG:: _mmm: + case PG::_mmmr: + case PG:: _b4: + case PG:: _4m: + case PG:: _4mm: + case PG::_b42m: + case PG::_b4m2: + case PG::_4mmm: + case PG:: _b3: + case PG:: _3m1: + case PG:: _31m: + case PG::_b3m1: + case PG::_b31m: + case PG:: _b6: + case PG:: _6m: + case PG:: _6mm: + case PG::_b62m: + case PG::_b6m2: + case PG::_6mmm: + case PG:: _mb3: + case PG::_b43m: + case PG::_mb3m: return false; + } + return false; + } + + //@brief : check if this point group has a mirror plane perpendicular to the z axis + //@return: true/false if there is/isn't a mirror perpendicular to the z axis + bool PointGroup::zMirror() const { + switch(type) { + case PG:: _1: + case PG:: _b1: + case PG:: _121: + case PG:: _112: + case PG:: _1m1: + case PG::_12m1: + case PG:: _222: + case PG::_222r: + case PG:: _mm2: + case PG::_mm2r: + case PG:: _4: + case PG:: _b4: + case PG:: _422: + case PG:: _4mm: + case PG::_b42m: + case PG::_b4m2: + case PG:: _3: + case PG:: _b3: + case PG:: _321: + case PG:: _312: + case PG:: _3m1: + case PG:: _31m: + case PG::_b3m1: + case PG::_b31m: + case PG:: _6: + case PG:: _622: + case PG:: _6mm: + case PG:: _23: + case PG:: _432: + case PG::_b43m: return false; + + case PG:: _11m: + case PG::_112m: + case PG:: _mmm: + case PG::_mmmr: + case PG:: _4m: + case PG::_4mmm: + case PG:: _b6: + case PG:: _6m: + case PG::_b6m2: + case PG::_b62m: + case PG::_6mmm: + case PG:: _mb3: + case PG::_mb3m: return true ; + } + return false; + } + + //@brief : check if this point group has a zRot() planes with normals in the equator (e.g. Nmm where N == zRot() ) + //@return: 0 - there are no mirrors with normals in the equatorial plane + // : 1 - there are are mirrors with normals in the equatorial plane with normal alignment (e.g. 31m and -4m2) + // : 2 - there are are mirrors with normals in the equatorial plane with rotated alignment (e.g. 31m and -42m) + uint_fast8_t PointGroup::mmType() const { + switch(type) { + case PG:: _1: return 0; + case PG:: _b1: return 0; + case PG:: _121: return 0; + case PG:: _112: return 0; + case PG:: _1m1: return 1; + case PG:: _11m: return 0; + case PG::_12m1: return 1; + case PG::_112m: return 0; + case PG:: _222: return 0; + case PG::_222r: return 0; + case PG:: _mm2: return 1; + case PG::_mm2r: return 2; + case PG:: _mmm: return 1; + case PG::_mmmr: return 2; + case PG:: _4: return 0; + case PG:: _b4: return 0; + case PG:: _4m: return 0; + case PG:: _422: return 0; + case PG:: _4mm: return 1; + case PG::_b42m: return 2; + case PG::_b4m2: return 1; + case PG::_4mmm: return 1; + case PG:: _3: return 0; + case PG:: _b3: return 0; + case PG:: _321: return 0; + case PG:: _312: return 0; + case PG:: _3m1: return 2; + case PG:: _31m: return 1; + case PG::_b3m1: return 2; + case PG::_b31m: return 1; + case PG:: _6: return 0; + case PG:: _b6: return 0; + case PG:: _6m: return 0; + case PG:: _622: return 0; + case PG:: _6mm: return 1; + case PG::_b6m2: return 2; + case PG::_b62m: return 1; + case PG::_6mmm: return 1; + case PG:: _23: return 0; + case PG:: _mb3: return 1; + case PG:: _432: return 0; + case PG::_b43m: return 2; + case PG::_mb3m: return 1; + } + return 0; + } + + //@brief : get rotational symmetry about z axis + //@return: degree of rotational symmetry about z + uint_fast8_t PointGroup::zRot() const { + switch(rotationGroup().type) { + case PG:: _1: + case PG:: _121: return 1; + case PG:: _112: return 2; + case PG:: _222: + case PG::_222r: + case PG:: _23: return 2; + case PG:: _3: + case PG:: _321: + case PG:: _312: return 3; + case PG:: _4: + case PG:: _422: + case PG:: _432: return 4; + case PG:: _6: + case PG:: _622: return 6; + default : throw std::logic_error("rotationGroup() '" + rotationGroup().name() + "' not handled for numRotOps()"); + } + } + + //@brief : get the closed set of rotational symmetry operators + //@return: number of rotational symmetry operators + uint_fast8_t PointGroup::numRotOps() const { + switch(rotationGroup().type) { + case PG:: _1: return 1; + case PG:: _121: + case PG:: _112: return 2; + case PG:: _222: + case PG::_222r: + case PG:: _4: return 4; + case PG:: _422: return 8; + case PG:: _3: return 3; + case PG:: _321: + case PG:: _312: + case PG:: _6: return 6; + case PG:: _622: + case PG:: _23: return 12; + case PG:: _432: return 24; + default : throw std::logic_error("rotationGroup() '" + rotationGroup().name() + "' not handled for numRotOps()"); + } + } + + //@brief : get the rotational symmetry operators + //@return: pointer to rotational symmetry operators (as w,x,y,z quaternions) + template + Real const * PointGroup::rotOps() const { + //cubic type symmetry symmetry operators + //1, 112, 121, 211, 222, 222r, 4, 422, 23, and 432 area all contigous subsets (with only 3 extra values beyond 432) + static Real const cub[27*4] = { + Real(0.00) , Real(1.00) , Real(0.00) , Real(0.00) ,//180 @ x [ 211 ] (duplicate for contigous 211 ) + Real(1.00) , Real(0.00) , Real(0.00) , Real(0.00) ,//identity [ 211 222r ] (duplicate for contigous 211 222r) + Real(0.00) , Real(0.00) , Real(0.00) , Real(1.00) ,//180 @ z [ 222r ] (duplicate for contigous 222r) + Real(0.00) , Constants::r1_2, Constants::r1_2, Real(0.00) ,//180 @ x,y [ 222r 422 432] + Real(0.00) ,-Constants::r1_2, Constants::r1_2, Real(0.00) ,//180 @ -x,y [ 222r 422 432] + Constants::r1_2, Real(0.00) , Real(0.00) , Constants::r1_2,// 90 @ z [ 4 422 432] + Constants::r1_2, Real(0.00) , Real(0.00) ,-Constants::r1_2,// 90 @ -z [ 4 422 432] + Real(0.00) , Real(0.00) , Real(0.00) , Real(1.00) ,//180 @ z [ 112 222 4 422 23 432] { 222r} + Real(1.00) , Real(0.00) , Real(0.00) , Real(0.00) ,//identity [1 112 121 222 4 422 23 432] {211 222r} + Real(0.00) , Real(0.00) , Real(1.00) , Real(0.00) ,//180 @ y [ 121 222 422 23 432] + Real(0.00) , Real(1.00) , Real(0.00) , Real(0.00) ,//180 @ x [ 222 422 23 432] {211 } + Real(0.50) , Real(0.50) , Real(0.50) , Real(0.50) ,//120 @ x, y, z [ 23 432] + Real(0.50) ,- Real(0.50) ,- Real(0.50) ,- Real(0.50) ,//120 @ -x,-y,-z [ 23 432] + Real(0.50) ,- Real(0.50) , Real(0.50) , Real(0.50) ,//120 @ -x, y, z [ 23 432] + Real(0.50) , Real(0.50) ,- Real(0.50) ,- Real(0.50) ,//120 @ x,-y,-z [ 23 432] + Real(0.50) , Real(0.50) ,- Real(0.50) , Real(0.50) ,//120 @ x,-y, z [ 23 432] + Real(0.50) ,- Real(0.50) , Real(0.50) ,- Real(0.50) ,//120 @ -x, y,-z [ 23 432] + Real(0.50) , Real(0.50) , Real(0.50) ,- Real(0.50) ,//120 @ x, y,-z [ 23 432] + Real(0.50) ,- Real(0.50) ,- Real(0.50) , Real(0.50) ,//120 @ -x,-y, z [ 23 432] + Real(0.00) , Real(0.00) , Constants::r1_2, Constants::r1_2,//180 @ y,z [ 432] + Real(0.00) , Real(0.00) ,-Constants::r1_2, Constants::r1_2,//180 @ -y,z [ 432] + Real(0.00) , Constants::r1_2, Real(0.00) , Constants::r1_2,//180 @ z,x [ 432] + Real(0.00) , Constants::r1_2, Real(0.00) ,-Constants::r1_2,//180 @ -z,x [ 432] + Constants::r1_2, Constants::r1_2, Real(0.00) , Real(0.00) ,// 90 @ x [ 432] + Constants::r1_2,-Constants::r1_2, Real(0.00) , Real(0.00) ,// 90 @ -x [ 432] + Constants::r1_2, Real(0.00) , Constants::r1_2, Real(0.00) ,// 90 @ y [ 432] + Constants::r1_2, Real(0.00) ,-Constants::r1_2, Real(0.00) ,// 90 @ -y [ 432] + }; + + //hexagonal type symmetry operators + //1, 3, 321, 312, 6, and 622 are all contigous subests (with only 3 extra values beyond 622) + static Real const hex[(12+3)*4] = { + Constants::r3_4, Real(0.00) , Real(0.00) , Real(0.50) ,// 60 @ z [ 6 622 ] + Constants::r3_4, Real(0.00) , Real(0.00) , -Real(0.50) ,// 60 @ -z [ 6 622 ] + Real(0.00) , Real(0.00) , Real(0.00) , Real(1.00) ,//180 @ z [ 6 622 ] (also in cub) + Real(1.00) , Real(0.00) , Real(0.00) , Real(0.00) ,//identity [1 3 321 6 622 ] (also in cub) {312} + Real(0.50) , Real(0.00) , Real(0.00) , Constants::r3_4,//120 @ z [ 3 321 6 622 ] {312} + Real(0.50) , Real(0.00) , Real(0.00) ,-Constants::r3_4,//120 @ -z [ 3 321 6 622 ] {312} + Real(0.00) , Real(1.00) , Real(0.00) , Real(0.00) ,//180 @ x [ 321 622 ] (also in cub) + Real(0.00) , Real(0.50) , Constants::r3_4, Real(0.00) ,//180 @ (x rotated 60 @ z) [ 321 622 ] + Real(0.00) , Real(0.50) ,-Constants::r3_4, Real(0.00) ,//180 @ (x rotated 60 @ -z) [ 321 622 ] + Real(0.00) , Constants::r3_4, Real(0.50) , Real(0.00) ,//180 @ (x rotated 30 @ z) [ 622 312] + Real(0.00) , Constants::r3_4, -Real(0.50) , Real(0.00) ,//180 @ (x rotated 30 @ -z) [ 622 312] + Real(0.00) , Real(0.00) , Real(1.00) , Real(0.00) ,//180 @ y [ 622 312] (also in cub) + Real(1.00) , Real(0.00) , Real(0.00) , Real(0.00) ,//identity [ 312] (duplicate for 312 block) + Real(0.50) , Real(0.00) , Real(0.00) , Constants::r3_4,//120 @ z [ 312] (duplicate for 312 block) + Real(0.50) , Real(0.00) , Real(0.00) ,-Constants::r3_4,//120 @ -z [ 312] (duplicate for 312 block) + }; + + //get correct operators based on symmetry + switch(rotationGroup().type) { + case PG:: _1: return cub + 8*4; + case PG:: _121: return cub + 8*4; + case PG:: _112: return cub + 7*4; + case PG:: _222: return cub + 7*4; + case PG::_222r: return cub + 1*4; + case PG:: _4: return cub + 5*4; + case PG:: _422: return cub + 3*4; + case PG:: _3: return hex + 3*4; + case PG:: _321: return hex + 3*4; + case PG:: _312: return hex + 9*4; + case PG:: _6: return hex + 0*4; + case PG:: _622: return hex + 0*4; + case PG:: _23: return cub + 7*4; + case PG:: _432: return cub + 3*4; + default : throw std::logic_error("rotationGroup() '" + rotationGroup().name() + "' not handled for rotOps()"); + } + } + + //@brief : get the number of mirror planes + //@return: number of mirror planes + uint_fast8_t PointGroup::numMirror() const { + switch(type) { + case PG:: _1: + case PG:: _b1: + case PG:: _121: + case PG:: _112: + case PG:: _222: + case PG::_222r: + case PG:: _4: + case PG:: _b4: + case PG:: _422: + case PG:: _3: + case PG:: _b3: + case PG:: _321: + case PG:: _312: + case PG:: _6: + case PG:: _622: + case PG:: _23: + case PG:: _432: return 0; + case PG:: _1m1: + case PG:: _11m: + case PG::_12m1: + case PG::_112m: + case PG:: _4m: + case PG:: _b6: + case PG:: _6m: return 1; + case PG:: _mm2: + case PG::_mm2r: + case PG::_b42m: + case PG::_b4m2: return 2; + case PG:: _mmm: + case PG::_mmmr: + case PG:: _3m1: + case PG:: _31m: + case PG::_b3m1: + case PG::_b31m: + case PG:: _mb3: return 3; + case PG:: _4mm: + case PG::_b62m: + case PG::_b6m2: return 4; + case PG::_4mmm: return 5; + case PG:: _6mm: + case PG::_b43m: return 6; + case PG::_6mmm: return 7; + case PG::_mb3m: return 9; + } + return 0; + } + + //@brief : get mirror plane normals + //@return: pointer to list of mirror plane normals (as x,y,z unit vectors) + template + Real const * PointGroup::mirrors() const { + //mirror planes for cubic type groups + //all cubic groups are a contigous subset + static Real const cub[11*3] = { + -Constants::r1_2, Constants::r1_2, 0 ,//-xy [ mmmr ] (duplicate for mmr) + Constants::r1_2, Constants::r1_2, 0 ,// xy [ mmmr ] (duplicate for mmr) + 0 , 0 , 1 ,// z [ 11m 112/m mmm mmmr 4/m 4/mmm m3 m3m] { 2mm m2m} + 0 , 1 , 0 ,// y [1m1 12/m1 mmm mm2 4mm -4m2 4/mmm m3 m3m] { 2mm } + 1 , 0 , 0 ,// x [ mmm mm2 4mm -4m2 4/mmm m3 m3m] {m11 2/m11 m2m} + -Constants::r1_2, Constants::r1_2, 0 ,//-xy [ (mmmr) mm2r 4mm -42m 4/mmm -43m m3m] + Constants::r1_2, Constants::r1_2, 0 ,// xy [ (mmmr) mm2r 4mm -42m 4/mmm -43m m3m] + 0 ,-Constants::r1_2, Constants::r1_2,//-yz [ -43m m3m] + 0 , Constants::r1_2, Constants::r1_2,// yz [ -43m m3m] + -Constants::r1_2, 0 , Constants::r1_2,//-xz [ -43m m3m] + Constants::r1_2, 0 , Constants::r1_2,// xz [ -43m m3m] + }; + + //mirror planes for hexagonal type groups + //all 3/6 fold groups are a contigous subset + static Real const hex[8*3] = { + 0 , 0 , 1,// z [ -6 6/m -62m 6/mmm] + 0 , 1 , 0,// y [ 31m -31m 6mm -62m 6/mmm] + Constants::r3_4, Real(0.5) , 0,// 30 [ 31m -31m 6mm -62m 6/mmm] + -Constants::r3_4, Real(0.5) , 0,//-30 [ 31m -31m 6mm -62m 6/mmm] + 1 , 0 , 0,// x [3m1 -3m1 6mm -6m2 6/mmm] + Real(0.5) , Constants::r3_4, 0,// 60 [3m1 -3m1 6mm -6m2 6/mmm] + - Real(0.5) , Constants::r3_4, 0,//-60 [3m1 -3m1 6mm -6m2 6/mmm] + 0 , 0 , 1,// z [ -6m2 ] (duplicate for -6m2) + }; + + switch(type) { + //no mirrors + case PG:: _1: + case PG:: _b1: + case PG:: _121: + case PG:: _112: + case PG:: _222: + case PG::_222r: + case PG:: _4: + case PG:: _b4: + case PG:: _422: + case PG:: _3: + case PG:: _b3: + case PG:: _321: + case PG:: _312: + case PG:: _6: + case PG:: _622: + case PG:: _23: + case PG:: _432: return NULL; + + //cubic types + case PG:: _1m1: return cub + 3*3; + case PG:: _11m: return cub + 2*3; + case PG::_12m1: return cub + 3*3; + case PG::_112m: return cub + 2*3; + case PG:: _mm2: return cub + 3*3; + case PG::_mm2r: return cub + 5*3; + case PG:: _mmm: return cub + 2*3; + case PG::_mmmr: return cub + 0*3; + case PG:: _4m: return cub + 2*3; + case PG:: _4mm: return cub + 3*3; + case PG::_b42m: return cub + 5*3; + case PG::_b4m2: return cub + 3*3; + case PG::_4mmm: return cub + 2*3; + case PG:: _mb3: return cub + 2*3; + case PG::_b43m: return cub + 5*3; + case PG::_mb3m: return cub + 2*3; + + //hex types + case PG:: _3m1: return hex + 4*3; + case PG:: _31m: return hex + 1*3; + case PG::_b3m1: return hex + 4*3; + case PG::_b31m: return hex + 1*3; + case PG:: _b6: return hex + 0*3; + case PG:: _6m: return hex + 0*3; + case PG:: _6mm: return hex + 1*3; + case PG::_b6m2: return hex + 4*3; + case PG::_b62m: return hex + 0*3; + case PG::_6mmm: return hex + 0*3; + } + return NULL; + } + + //@brief : get the number of rotational symmetry operators + //@return: number of rotational symmetry operators + uint_fast8_t PointGroup::numRotAxis() const { + switch(rotationGroup().type) { + case PG:: _1: return 0; + case PG:: _121: + case PG:: _112: + case PG:: _3: + case PG:: _4: + case PG:: _6: return 1; + case PG:: _222: + case PG::_222r: return 3; + case PG:: _321: + case PG:: _312: return 4; + case PG:: _422: return 5; + case PG:: _622: + case PG:: _23: return 7; + case PG:: _432: return 13; + default : throw std::logic_error("rotationGroup() '" + rotationGroup().name() + "' not handled for numRotOps()"); + } + } + + //@brief : get the rotational symmetry axis + //@return: pointer to rotational symmetry axis (as n,x,y,z where xyz is a unit axis and n is the order (negative for rotoinversion)) + template + Real const * PointGroup::rotAxis() const { + //cubic 432 symmetry operators (1, 2, 222, 4, 422, and 23 are all a subset of these operators) + //1, 121, 222, and 23 are in contiguous blocks as ordered + static Real const cub[64] = { + 2, Constants::r1_2, Constants::r1_2, 0,//2 fold @ xy + 2,-Constants::r1_2, Constants::r1_2, 0,//2 fold @ -xy + 2, Constants::r1_2, 0, Constants::r1_2,//2 fold @ xz + 2,-Constants::r1_2, 0, Constants::r1_2,//2 fold @ -xz + 2, 0, Constants::r1_2, Constants::r1_2,//2 fold @ yz + 2, 0,-Constants::r1_2, Constants::r1_2,//2 fold @ -yz + 4, 0, 0, 1,//4 fold @ z + 4, 0, 1, 0,//4 fold @ y + 4, 1, 0, 0,//4 fold @ x + 3, Constants::r1_3, Constants::r1_3, Constants::r1_3,//3 fold @ xyz + 3,-Constants::r1_3, Constants::r1_3, Constants::r1_3,//3 fold @ -x yz + 3,-Constants::r1_3,-Constants::r1_3, Constants::r1_3,//3 fold @ -x-yz + 3, Constants::r1_3,-Constants::r1_3, Constants::r1_3,//3 fold @ x-yz + 2, 1, 0, 0,//2 fold @ x + 2, 0, 1, 0,//2 fold @ y + 2, 0, 0, 1,//2 fold @ z + }; + + static Real const cubI[64] = { + cub[4* 0+0], cub[4* 0+1], cub[4* 0+2], cub[4* 0+3], + cub[4* 1+0], cub[4* 1+1], cub[4* 1+2], cub[4* 1+3], + cub[4* 2+0], cub[4* 2+1], cub[4* 2+2], cub[4* 2+3], + cub[4* 3+0], cub[4* 3+1], cub[4* 3+2], cub[4* 3+3], + cub[4* 4+0], cub[4* 4+1], cub[4* 4+2], cub[4* 4+3], + cub[4* 5+0], cub[4* 5+1], cub[4* 5+2], cub[4* 5+3], + cub[4* 6+0], cub[4* 6+1], cub[4* 6+2], cub[4* 6+3], + cub[4* 7+0], cub[4* 7+1], cub[4* 7+2], cub[4* 7+3], + cub[4* 8+0], cub[4* 8+1], cub[4* 8+2], cub[4* 8+3], + -cub[4* 9+0], cub[4* 9+1], cub[4* 9+2], cub[4* 9+3], + -cub[4*10+0], cub[4*10+1], cub[4*10+2], cub[4*10+3], + -cub[4*11+0], cub[4*11+1], cub[4*11+2], cub[4*11+3], + -cub[4*12+0], cub[4*12+1], cub[4*12+2], cub[4*12+3], + cub[4*13+0], cub[4*13+1], cub[4*13+2], cub[4*13+3], + cub[4*14+0], cub[4*14+1], cub[4*14+2], cub[4*14+3], + cub[4*15+0], cub[4*15+1], cub[4*15+2], cub[4*15+3], + }; + + static Real const hex[48] = { + 6, 0, 0, 1,//z + 2, 0, 1, 0,//y + 2, Constants::r3_4, Real(0.5) , 0,// 60 + 2, -Constants::r3_4, Real(0.5) , 0,//-60 + 2, 1, 0, 0,//x + 2, Real(0.5) , Constants::r3_4, 0,// 30 + 2, - Real(0.5) , Constants::r3_4, 0,//-30 + 3, 0, 0, 1,//z (duplicate so more point groups are subset of this) + 2, 0, 1, 0,//y (duplicate so more point groups are subset of this) + 2, Constants::r3_4, Real(0.5) , 0,// 60 (duplicate so more point groups are subset of this) + 2, -Constants::r3_4, Real(0.5) , 0,//-60 (duplicate so more point groups are subset of this) + }; + + static Real const hexI[48] = { + -hex[4* 0+0], hex[4* 0+1], hex[4* 0+2], hex[4* 0+3], + hex[4* 1+0], hex[4* 1+1], hex[4* 1+2], hex[4* 1+3], + hex[4* 2+0], hex[4* 2+1], hex[4* 2+2], hex[4* 2+3], + hex[4* 3+0], hex[4* 3+1], hex[4* 3+2], hex[4* 3+3], + hex[4* 4+0], hex[4* 4+1], hex[4* 4+2], hex[4* 4+3], + hex[4* 5+0], hex[4* 5+1], hex[4* 5+2], hex[4* 5+3], + hex[4* 6+0], hex[4* 6+1], hex[4* 6+2], hex[4* 6+3], + -hex[4* 7+0], hex[4* 7+1], hex[4* 7+2], hex[4* 7+3], + hex[4* 8+0], hex[4* 8+1], hex[4* 8+2], hex[4* 8+3], + hex[4* 9+0], hex[4* 9+1], hex[4* 9+2], hex[4* 9+3], + hex[4*10+0], hex[4*10+1], hex[4*10+2], hex[4*10+3], + }; + + static Real const b62[28] = { + hex[4* 4+0], hex[4* 4+1], hex[4* 4+2], hex[4* 4+3],// 2 @ 30 + hex[4* 5+0], hex[4* 5+1], hex[4* 5+2], hex[4* 5+3],// 2 @ -30 + hex[4* 6+0], hex[4* 6+1], hex[4* 6+2], hex[4* 6+3],// 2 @ x + -hex[4* 0+0], hex[4* 0+1], hex[4* 0+2], hex[4* 0+3],// 6 @ z + hex[4* 1+0], hex[4* 1+1], hex[4* 1+2], hex[4* 1+3],// 2 @ 60 + hex[4* 2+0], hex[4* 2+1], hex[4* 2+2], hex[4* 2+3],// 2 @ -60 + hex[4* 3+0], hex[4* 3+1], hex[4* 3+2], hex[4* 3+3],// 2 @ y + }; + + static Real const tet[24] = { + cub[4*13+0], cub[4*13+1], cub[4*13+2], cub[4*13+3],//2 @ x + cub[4*14+0], cub[4*14+1], cub[4*14+2], cub[4*14+3],//2 @ y + cub[4* 6+0], cub[4* 6+1], cub[4* 6+2], cub[4* 6+3],//4 @ z + cub[4* 0+0], cub[4* 0+1], cub[4* 0+2], cub[4* 0+3],//2 @ xy + cub[4* 1+0], cub[4* 1+1], cub[4* 1+2], cub[4* 1+3],//2 @ -xy + cub[4*15+0], cub[4*15+1], cub[4*15+2], cub[4*15+3],//2 @ z (extra for 222r) + }; + + static Real const b43[28] = { + -cub[4* 6+0], cub[4* 6+1], cub[4* 6+2], cub[4* 6+3],//4 @ z + -cub[4* 7+0], cub[4* 7+1], cub[4* 7+2], cub[4* 7+3],//4 @ y + -cub[4* 8+0], cub[4* 8+1], cub[4* 8+2], cub[4* 8+3],//4 @ z + cub[4* 9+0], cub[4* 9+1], cub[4* 9+2], cub[4* 9+3],//3 fold @ xyz + cub[4*10+0], cub[4*10+1], cub[4*10+2], cub[4*10+3],//3 fold @ -x yz + cub[4*11+0], cub[4*11+1], cub[4*11+2], cub[4*11+3],//3 fold @ -x-yz + cub[4*12+0], cub[4*12+1], cub[4*12+2], cub[4*12+3],//3 fold @ x-yz + }; + + static Real const b42[20] = { + cub[4*13+0], cub[4*13+1], cub[4*13+2], cub[4*13+3],//2 @ x + cub[4*14+0], cub[4*14+1], cub[4*14+2], cub[4*14+3],//2 @ y + -cub[4* 6+0], cub[4* 6+1], cub[4* 6+2], cub[4* 6+3],//4 @ z + cub[4* 0+0], cub[4* 0+1], cub[4* 0+2], cub[4* 0+3],//2 @ xy + cub[4* 1+0], cub[4* 1+1], cub[4* 1+2], cub[4* 1+3],//2 @ -xy + }; + + //get correct operators based on symmetry + switch(type) { + case PG:: _1: return NULL; + case PG:: _1m1: return NULL; + case PG:: _11m: return NULL; + case PG:: _b1: return NULL; + case PG:: _121: return cub +56;//14 + case PG:: _112: return cub +60;//15 + case PG::_12m1: return cub +56;//14 + case PG::_112m: return cub +60;//15 + case PG:: _222: return cub +52;//last 3 + case PG::_222r: return tet +12;//last 3 + case PG:: _mm2: return cub +60;//15 + case PG::_mm2r: return cub +60;//15 + case PG:: _mmm: return cub +52;//last 3 + case PG::_mmmr: return tet +12;//last 3 + case PG:: _4: return cub +24;//2 + case PG:: _b4: return b43 ;//1 + case PG:: _4m: return cub +24;//2 + case PG:: _422: return tet ;//first 5 + case PG:: _4mm: return tet +8;//2 + case PG::_b42m: return b42 ;//first 3 + case PG::_b4m2: return b42 +8;//last 3 + case PG::_4mmm: return tet ;//first 5 + case PG:: _3: return hex +28;//7 + case PG:: _b3: return hexI+28;//7 + case PG:: _321: return hex +16;//4->7 + case PG:: _312: return hex +28;//7->10 + case PG:: _3m1: return hex +28;//7 + case PG:: _31m: return hex +28;//7 + case PG::_b3m1: return hexI+16;//4->7 + case PG::_b31m: return hexI+28;//7->10 + case PG:: _6: return hex ;//first 1 + case PG:: _6m: return hex ;//first 1 + case PG:: _b6: return hexI ;//first 1 + case PG:: _622: return hex ;//first 7 + case PG:: _6mm: return hex ;//first 1 + case PG::_b62m: return b62 ;//first 4 + case PG::_b6m2: return b62 +12;//last 4 + case PG::_6mmm: return hex ;//first 7 + case PG:: _23: return cub +36;//last 7 + case PG:: _mb3: return cubI+36;//last 7 + case PG:: _432: return cub ;//first 13 + case PG::_b43m: return b43 ;//all + case PG::_mb3m: return cubI ;//first 13 + } + return NULL; + } + + //////////////////////////////////////////////////////////////////////// + // Symmetry Operations // + //////////////////////////////////////////////////////////////////////// + + //@brief : check if a rodrigues vector is in the fundamental zone + //@param ro: orientation to check (x, y, z, tan(w/2)) + //@return : true/false if ro is/isn't in the fundamental zone + template + bool PointGroup::roInFz(Real const * const ro) const { + switch(rotationGroup().type) { + case PG:: _1: return PointGroup::FZ1 (ro); + case PG:: _121: return PointGroup::FZ121 (ro); + case PG:: _112: return PointGroup::FZ112 (ro); + case PG:: _222: return PointGroup::FZ222 (ro); + case PG::_222r: return PointGroup::FZ222r(ro); + case PG:: _4: return PointGroup::FZ4 (ro); + case PG:: _422: return PointGroup::FZ422 (ro); + case PG:: _3: return PointGroup::FZ3 (ro); + case PG:: _321: return PointGroup::FZ321 (ro); + case PG:: _312: return PointGroup::FZ312 (ro); + case PG:: _6: return PointGroup::FZ6 (ro); + case PG:: _622: return PointGroup::FZ622 (ro); + case PG:: _23: return PointGroup::FZ23 (ro); + case PG:: _432: return PointGroup::FZ432 (ro); + default : throw std::logic_error("rotationGroup() '" + rotationGroup().name() + "' not handled for roInFz()"); + } + return false; + } + + //@brief : compute the symmetric equivalent orientation in the fundamental zone + //@param qu: orientation to compute symmetric equivalent of + //@param fz: location to write symmetric equivalent + template + void PointGroup::fzQu(Real const * const qu, Real * const fz) const { + //Real ro[4];//use rodrigues vector to check if we're inside the FZ + const Real q0[4] = {qu[0], qu[1], qu[2], qu[3]};//save input in case qu and fz are the same + const uint_fast8_t num = numRotOps();//get number of symmetry operators once + Real const * const ops = rotOps();//get symmetry operators once + + std::copy(qu, qu+4, fz); + Real quI[4]; + for(uint_fast8_t i = 0; i < num; i++) {//loop over symmetry operators + quat::mul (ops+4*i, q0, quI);//compute operator * qu (this is for passive rotations, active would be q * O_sym) + quat::expl(quI, quI);//select north hemisphere quat (0 <= rotation <= pi) + if(quI[0] > fz[0]) { + std::copy(quI, quI+4, fz); + //this is currently commented since the unit test checks against roInFz + // qu2ro(fz, ro);//convert to rodrigues vector (for FZ check) + // if(roInFz(ro)) return;//stop if we're in the fundamental zone + } + } + } + + //@brief : compute the disorientation between 2 orientations + //@param qu1: first orientation to compute disorientation of (as w,x,y,z quaternion) + //@param qu2: second orientation to compute disorientation of (as w,x,y,z quaternion) + //@param dis: location to write disorientation (as w,x,y,z quaternion) + //@note : qu2 is assumed to belong to the same point group + template + void PointGroup::disoQu(Real const * const qu1, Real const * const qu2, Real * const dis) const { + switch(rotationGroup().type) { + case PG:: _432: return Diso432(qu1, qu2, dis); + default : return DisoQu(qu1, qu2, dis, rotOps(), numRotOps(), rotOps(), numRotOps()); + } + } + + //@brief: compute the symmetric equivalent orientation of qu2 closest to qu1 + //@param qu1: orientation to search near + //@param qu2: orientation to compute symmetric equivalent of + //@param qu3: location to write symmetric equivalent of qu2 closest to qu1 + template + void PointGroup::nearbyQu(Real const * const qu1, Real const * const qu2, Real * const qu3) const { + Real work[4]; + Real maxDot = Real(-1); + const uint_fast8_t num = numRotOps();//get number of symmetry operators + Real const * const ops = rotOps();//get symmetry operators + for(uint_fast8_t i = 0; i < num; i++) {//loop over symmetry operators + quat::mul(ops + 4 * i, qu2, work);//comput3e symmetric equivalent of qu2 + const Real dot = std::fabs(quat::dot(qu1, work));//compute how close we are to qu1 + if(dot > maxDot) {//this is the closest symmetric equivalent of qu2 so far + maxDot = dot;//update best + std::copy(work, work + 4, qu3);//save equivalent + } + } + quat::expl(qu3, qu3);//restrict rotation to [0,pi] + } + + //@brief : reduce a unit direction to the fundamental sector + //@param n : unit direction to reduce (magnitude is assumed to be 1) + //@param fs: location to write symmetric equivalent of n in the fundamental sector (can be the same as n) + //@param : true if n was already in the fundamental sector + template + bool PointGroup::fsDir(Real const * const n, Real * const fs) const { + std::copy(n, n+3, fs); + switch(type) { + case PG:: _1: return true; + case PG:: _b1: return fs::b1 (fs); + case PG:: _121: return fs::_121 (fs); + case PG:: _112: return fs::_112 (fs); + case PG:: _1m1: return fs::_1m1 (fs); + case PG:: _11m: return fs::_11m (fs); + case PG::_12m1: return fs::_12m1(fs); + case PG::_112m: return fs::_112m(fs); + case PG:: _222: return fs::_222 (fs); + case PG::_222r: return fs::_222r(fs); + case PG:: _mm2: return fs:: mm2 (fs); + case PG::_mm2r: return fs:: mm2r(fs); + case PG:: _mmm: return fs:: mmm (fs); + case PG::_mmmr: return fs:: mmmr(fs); + case PG:: _4: return fs::_4 (fs); + case PG:: _b4: return fs::b4 (fs); + case PG:: _4m: return fs::_4m (fs); + case PG:: _422: return fs::_422 (fs); + case PG:: _4mm: return fs::_4mm (fs); + case PG::_b42m: return fs::b42m (fs); + case PG::_b4m2: return fs::b4m2 (fs); + case PG::_4mmm: return fs::_4mmm(fs); + case PG:: _3: return fs::_3 (fs); + case PG:: _b3: return fs::b3 (fs); + case PG:: _321: return fs::_321 (fs); + case PG:: _312: return fs::_312 (fs); + case PG:: _3m1: return fs::_3m1 (fs); + case PG:: _31m: return fs::_31m (fs); + case PG::_b3m1: return fs::b3m1 (fs); + case PG::_b31m: return fs::b31m (fs); + case PG:: _6: return fs::_6 (fs); + case PG:: _b6: return fs::b6 (fs); + case PG:: _6m: return fs::_6m (fs); + case PG:: _622: return fs::_622 (fs); + case PG:: _6mm: return fs::_6mm (fs); + case PG::_b6m2: return fs::b6m2 (fs); + case PG::_b62m: return fs::b62m (fs); + case PG::_6mmm: return fs::_6mmm(fs); + case PG:: _23: return fs::_23 (fs); + case PG:: _mb3: return fs:: mb3 (fs); + case PG:: _432: return fs::_432 (fs); + case PG::_b43m: return fs::b43m (fs); + case PG::_mb3m: return fs::mb3m (fs); + } + return true; + } + + //@param n : crystal direction to color to color (magnitude is assumed to be 1) + //@param rgb: location to write color [0,1] + //@param h2r: hsl2rgb like coloring function to use with h, s, and l in [0,1] and output as [0,1] rgb: void(Real const * const hsl, Real * const rgb) + template + void PointGroup::ipfColor(Real const * const n, Real * const rgb, std::function h2r) const { + //build a list of commonly needed ipf color corners + static const Real nz [3] = { Real(0.0) , Real(0.0) , Real(-1) }; + static const Real y12 [3] = { Constants::r3_4, Real(0.5) , Real( 0) };// 30 degrees + static const Real y8 [3] = { Constants::r1_2, Constants::r1_2, Real( 0) };// 45 degrees + static const Real n8 [3] = {-Constants::r1_2, Constants::r1_2, Real( 0) };//135 degrees + static const Real y6 [3] = { Real(0.5) , Constants::r3_4, Real( 0) };// 60 degrees + static const Real y3 [3] = {- Real(0.5) , Constants::r3_4, Real( 0) };//120 degrees + static const Real xy [3] = { Constants::r1_3, Constants::r1_3, Constants::r1_3};// 111 + static const Real nxy [3] = {-Constants::r1_3, Constants::r1_3, Constants::r1_3};//-111 + static const Real xz [3] = { Constants::r1_2, Real(0.0) , Constants::r1_2};// 101 + static const Real dirs[4][3] = { + Real(0), Real( 0), Real(1),// z + Real(0), Real(-1), Real(0),//-y + Real(1), Real( 0), Real(0),// x + Real(0), Real( 1), Real(0),// y + }; + + //triangles (when there are 2 or more rotation axis) + static const detail::SphericalTriangle b3 (dirs[0], dirs[2], y3 );//120 degrees + static const detail::SphericalTriangle c2 (dirs[0], dirs[2], dirs[3]);// 90 degrees + static const detail::SphericalTriangle c2b(dirs[0], y8 , n8 );// c2 rotated 45 @ z + static const detail::SphericalTriangle c3 (dirs[0], dirs[2], y6 );// 60 degrees + static const detail::SphericalTriangle c3b(dirs[0], y12 , dirs[3]);// c3 rotated 30 @ z + static const detail::SphericalTriangle c4 (dirs[0], dirs[2], y8 );// 45 degrees + static const detail::SphericalTriangle c6 (dirs[0], dirs[2], y12 );// 30 degrees + static const detail::SphericalTriangle m3 (dirs[0], xy , nxy );//cubic low triangle + static const detail::SphericalTriangle m3m(dirs[0], xz , xy );//cubic triangle + + //wedges (when there is only 1 rotation axis) + static const detail::SphericalWedge< Real> w2 (dirs[2], dirs[3]);//2 fold wedge + static const detail::SphericalWedge< Real> w2b(y8 , n8 );//2 fold wedge + static const detail::SphericalPatch<4,Real> w2c(dirs , xz );//alternate 2 fold wedge + static const detail::SphericalWedge< Real> w4 (dirs[2], y8 );//4 fold wedge + static const detail::SphericalWedge< Real> w3a(dirs[2], y6 );//3 fold wedge + static const detail::SphericalWedge< Real> w3b(y12 , dirs[3]);//w3a rotated 30 @ z + static const detail::SphericalWedge< Real> w6 (dirs[2], y12 );//6 fold wedge + + //@brief : convert from unit direction to fractional hsl + //@param n : unit direction to convert to color [in place] + //@param inv: true if inversion symmetry should be applied by doubling the polar angle + static const std::function n2hsl = [](Real*const n, const bool inv) { + //convert from unit direction to fractional spherical coordinates + Real theta = std::atan2(n[1], n[0]) / Constants::pi2;//[-0.5,0.5] + if(std::signbit(theta)) theta += Real(1);//[0,1] + Real phi = std::acos(n[2]) / Constants::pi;//[0,1] + if(inv) phi *= Real(2);//inversion via doubling polar angle + + //convert from fractional sphercal coordinate to fractional hs; + n[0] = theta ;//h + n[1] = Real(1) ;//s + n[2] = Real(1) - phi;//l + }; + + //compute ipf color + fsDir(n, rgb);//temporarily store fundamental sector direction in rgb + bool wCen = true, iTht = false, inv = false; + switch(type) { + case PG:: _b1: inv = true; + case PG:: _1: + case PG:: _11m: n2hsl(rgb, inv); return h2r(rgb, rgb); + + case PG:: _121: wCen = fs::_m11(rgb); + return w2c.toColor(rgb, rgb, h2r, wCen); + + case PG:: _112: wCen = fs::_m11(rgb); + case PG:: _mm2: return w2 .toColor(rgb, rgb, h2r, wCen); + + case PG:: _1m1: std::rotate(rgb, rgb+2, rgb+3); n2hsl(rgb, inv); return h2r(rgb, rgb); + + case PG::_12m1: + case PG::_112m: + case PG:: _222: wCen = fs::mmm (rgb); + case PG:: _mmm: return c2 .toColor(rgb, rgb, h2r, wCen); + + case PG::_222r: wCen = fs::mmmr(rgb); + case PG::_mmmr: return c2b.toColor(rgb, rgb, h2r, wCen); + + case PG::_mm2r: return w2b.toColor(rgb, rgb, h2r, wCen); + + case PG:: _4: wCen = fs::_4mm(rgb); + case PG:: _4mm: return w4 .toColor(rgb, rgb, h2r, wCen); + + case PG:: _b4: inv = true; + if(std::signbit(rgb[2])) { + rgb[2] = -rgb[2]; + if(std::signbit(rgb[0])) { + rgb[0] = -rgb[0]; + rgb[1] = -rgb[1]; + } + } else { + std::swap(rgb[0], rgb[1]); + rgb[1] = -rgb[1]; + } + w2c.toHemi(rgb, rgb[0], rgb[2]); + rgb[1] = Real(1); + rgb[2] = Real(1) - rgb[2] * 2; + return h2r(rgb, rgb); + + case PG:: _4m: + case PG:: _422: wCen = fs::_4mm(rgb); + return c4 .toColor(rgb, rgb, h2r, wCen); + + case PG::_b42m: wCen = !std::signbit(rgb[0]); fs::_4mm(rgb); + return c4 .toColor(rgb, rgb, h2r, wCen); + + case PG::_b4m2: wCen = fs::_4mm(rgb) && wCen; + case PG::_4mmm: return c4 .toColor(rgb, rgb, h2r, wCen); + + case PG:: _3: wCen = fs::_31m(rgb); + case PG:: _31m: return w3a.toColor(rgb, rgb, h2r, wCen); + + case PG:: _b3: b3.toHemi(rgb, rgb[0], rgb[2]); + rgb[1] = Real(1); + rgb[2] = Real(1) - rgb[2] * 2; + return h2r(rgb, rgb); + + case PG:: _321: + case PG:: _b6: wCen = fs::_31m(rgb); + case PG::_b62m: return c3 .toColor(rgb, rgb, h2r, wCen); + + case PG:: _312: wCen = fs::_m11(rgb); + case PG::_b6m2: return c3b.toColor(rgb, rgb, h2r, wCen, true); + + case PG:: _3m1: return w3b.toColor(rgb, rgb, h2r, wCen, true); + + case PG::_b3m1: wCen = fs::_31m(rgb); fs::_6mm(rgb); + return c6 .toColor(rgb, rgb, h2r, wCen); + + case PG::_b31m: iTht = true; + case PG:: _6m: + case PG:: _622: wCen = fs::_6mm(rgb); + case PG::_6mmm: return c6 .toColor(rgb, rgb, h2r, wCen, iTht); + + case PG:: _6: wCen = fs::_6mm(rgb); + case PG:: _6mm: return w6 .toColor(rgb, rgb, h2r, wCen); + + case PG:: _23: wCen = fs::mm2r(rgb); + case PG::_b43m: return m3 .toColor(rgb, rgb, h2r, wCen); + + case PG:: _mb3: + case PG:: _432: wCen = fs::_4mm(rgb); + case PG::_mb3m: return m3m.toColor(rgb, rgb, h2r, wCen); + } + } + + + //////////////////////////////////////////////////////////////////////// + // Static Functions for Symmetry Specific Code // + //////////////////////////////////////////////////////////////////////// + + namespace detail { + //@brief: check if |a * b| <= c handling a == 0 and b == inf + //@param a: a (may be 0) + //@param b: b (may be inf) + //@param c: c (assumed to be >= 0) + //@return : |a * b| <= c with 0 * inf == 0 instead of nan + template + bool cmpInf(const Real a, const Real b, const Real c) { + return std::isinf(b) ? 0 == a : a * b <= c; + } + } + + //@brief : check if an orientation is in the fundamental zone + //@param ro: orientation to check as [x, y, z, tan(w/2)] + //@return : true/false if the orientation is/isn't in the fundamental zone + template bool PointGroup::FZ121 (Real const * const ro) {return FZ1(ro) && detail::cmpInf( std::fabs(ro[1]), ro[3], Real(1));}// | ro(y) | <= 1 (correcting for 0 * inf == nan) + template bool PointGroup::FZ112 (Real const * const ro) {return FZ1(ro) && detail::cmpInf( std::fabs(ro[2]), ro[3], Real(1));}// | ro(z) | <= 1 (correcting for 0 * inf == nan) + template bool PointGroup::FZ222 (Real const * const ro) {return FZ1(ro) && detail::cmpInf( std::max( std::fabs(ro[0]), std::max( std::fabs(ro[1]), std::fabs(ro[2]) ) ), ro[3], Real(1));}// max(ro) <= 1 + template bool PointGroup::FZ222r(Real const * const ro) {return FZ112(ro) && detail::cmpInf( std::fabs(ro[0]) + std::fabs(ro[1]), ro[3], Constants::r2);} + template bool PointGroup::FZ3 (Real const * const ro) {return FZ1(ro) && detail::cmpInf( std::fabs(ro[2]), ro[3], Constants::r1_3);}//z cutting planes at +/- tan(pi/6) + template bool PointGroup::FZ4 (Real const * const ro) {return FZ1(ro) && detail::cmpInf( std::fabs(ro[2]), ro[3], Constants::tp_8);}//z cutting planes at +/- tan(pi / (2 * N)) + template bool PointGroup::FZ422 (Real const * const ro) {return FZ4(ro) && FZ222(ro) & FZ222r(ro);} + template bool PointGroup::FZ321 (Real const * const ro) {return FZ3(ro) && FZ3xy(ro, false);} + template bool PointGroup::FZ312 (Real const * const ro) {return FZ3(ro) && FZ3xy(ro, true );} + template bool PointGroup::FZ6 (Real const * const ro) {return FZ1(ro) && detail::cmpInf(std::fabs(ro[2]), ro[3], Constants::tp_12);}//z cutting planes at +/- tan(pi / (2 * N)) + template bool PointGroup::FZ622 (Real const * const ro) {return FZ6(ro) && FZ3xy(ro, false) & FZ3xy(ro, true);} + + //@brief : check if an orientation is in the fundamental zone + //@param ro: orientation to check as [x, y, z, tan(w/2)] + //@return : true/false if the orientation is/isn't in the fundamental zone + //@note : 23 group + template + bool PointGroup::FZ23 (Real const * const ro) { + if(std::signbit(ro[3])) return false;//rotation <= pi + const Real xyz[3] = {std::fabs(ro[0]), std::fabs(ro[1]), std::fabs(ro[2])};//element wise absolute value + return detail::cmpInf( std::accumulate(xyz, xyz+3, Real(0)), ro[3], Real(1));//check against octohedron with faces at tan(pi/8) + } + + //@brief : check if an orientation is in the fundamental zone + //@param ro: orientation to check as [x, y, z, tan(w/2)] + //@return : true/false if the orientation is/isn't in the fundamental zone + //@note : 432 group + template + bool PointGroup::FZ432(Real const * const ro) { + if(std::signbit(ro[3])) return false;//rotation <= pi + const Real xyz[3] = {std::fabs(ro[0]), std::fabs(ro[1]), std::fabs(ro[2])};//element wise absolute value + const Real vMax = *std::max_element(xyz, xyz+3);//get largest absolute value element + if(vMax * ro[3] > Constants::tp_8) return false;//check against cube faces of cube octohedron at tan(pi/8) + return detail::cmpInf( std::accumulate(xyz, xyz+3, Real(0)), ro[3], Real(1));//check against octohedral faces cube octohedron at tan(pi/6) + } + + //@brief : test x and y against 60 degree ro fz cutting plane + //@param ro: orientation to check as [x, y, z, tan(w/2)] + //@param yx: true to swap x and y + //@return : true if inside fundamental zone + template + bool PointGroup::FZ3xy (Real const * const ro, const bool yx) { + const Real x2 = std::fabs(ro[yx ? 1 : 0] * ro[3]) / 2; + const Real y34 = std::fabs(ro[yx ? 0 : 1] * ro[3]) * Constants::r3_4; + return y34 > x2 ? x2 + y34 < Real(1) : x2 <= Real(0.5); + } + + //@brief : compute the disorientation between 2 orientations + //@param qu1: first orientation to compute disorientation of + //@param qu2: second orientation to compute disorientation of + //@param dis: location to write disorientation + //@param op1: rotational symmetry operators as quaternions (wxyz) for the first orientation + //@param no1: number of rotational symmetry operators for the first orientation + //@param op2: rotational symmetry operators as quaternions (wxyz) for the second orientation + //@param no2: number of rotational symmetry operators for the second orientation + template + void PointGroup::DisoQu(Real const * const qu1, Real const * const qu2, Real * const dis, Real const * const op1, const uint_fast8_t no1, Real const * const op2, const uint_fast8_t no2) { + Real delQ[4], work[4];//qu1 * conjg(qu2) iDeltaQ[4]; + quat::conj(qu2, work);//store conj(qu2) in work + quat::mul (qu1, work, delQ);//compute qu1 * conjg(qu2) + std::copy(delQ, delQ+4, dis);//start with the result for no symmetry + //this loop (O_sym * qu1 * conj(qu2) * conj(O_sym)) is for passive rotations + //active rotations are qu1 * O_sym * conj(O_sym) * conj(qu2) ==> qu1 * O_sym * conj(qu2) + for(uint_fast8_t i = 0; i < no1; i++) {//loop over qu1 symmetry + quat::mul (op1+4*i, delQ, work);//compute (symmetric equivalent of qu1) * conj(qu2) + for(uint_fast8_t j = 0; j < no2; j++) {//loop over qu2 symmetry + quat::mul (work, op2+4*j, work);//compute (symmetric equivalent of qu1) * (symmetric equivalent of conj(qu2)) + quat::expl(work, work);//restrict rotation to [0,pi] + if(work[0] > dis[0]) std::copy(work, work+4, dis);//save the result if it is the lowest rotation angle so far + } + } + } + + //@brief : compute the disorientation between 2 cubic (432) orientations + //@param qu1: first orientation to compute disorientation of + //@param qu2: second orientation to compute disorientation of + //@param dis: location to write disorientation + //@note : this shortcut is significantly faster than the general algorithm + template + void PointGroup::Diso432(Real const * const qu1, Real const * const qu2, Real * const dis) { + //compute 2 potential rotations once + static const Real qu3[4] = { Real(0.5) ,- Real(0.5) , -Real(0.5), -Real(0.5)}; + static const Real qu4[4] = {Constants::r1_2,-Constants::r1_2, Real(0.0), Real(0.0)}; + + //compute initial misorientation + quat::conj(qu2, dis);//store conj(qu2) in output temporarily + quat::mul (qu1, dis, dis);//compute \Delta{qu} = qu1 * qu2^-1 + quat::cAbs(dis, dis);//element wise absolute value of misorientation + std::sort(dis, dis+4, std::greater());//sort misorientation from largest to smallest (from 2 fold axis) + + //now check for improvements from 3 and 4 fold axis + const Real s10 = dis[1] + dis[0]; + const Real op3 = (dis[3] + dis[2] + s10) * qu3[0];//rotate by 3 fold axis + const Real op4 = s10 * qu4[0];//rotate by 4 fold axis + if(op3 >= op4 && op3 > dis[0]) {//3 fold is best + quat::mul (dis, qu3, dis);//apply 3 fold axis + quat::cAbs(dis, dis);//element wise abs + std::sort(dis, dis+4, std::greater());//sort large -> small + } else if(op4 > op3 && op4 > dis[0]) {//4 fold is best + quat::mul (dis, qu4, dis);//apply 4 fold axis + quat::cAbs(dis, dis);//element wise abs + std::sort(dis, dis+4, std::greater());//sort large -> small + } + } +} + +#endif//_symmetry_h_ diff --git a/include/xtal/vendor/emsoft.hpp b/include/xtal/vendor/emsoft.hpp new file mode 100644 index 0000000..7ea0007 --- /dev/null +++ b/include/xtal/vendor/emsoft.hpp @@ -0,0 +1,192 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + +#ifndef _emsoft_h_ +#define _emsoft_h_ + +#include +#include +#include + +#include "xtal/rotations.hpp" + +namespace emsoft { + + template + struct AngleFile { + + //@brief : get the format of the stored rotations + //@return: format as one of Rotation enumerate + xtal::Rotation getType() const {return rot;} + + //@brief : get the number of rotations + //@return: number of stored rotations + size_t getNum() const {return ang.size() / xtal::rotLen(rot);} + + //@brief : get the ith rotation + //@param i: rotation to get (no bounds check) + //@return : pointer to ith rotation + Real const * operator[](const size_t i) const {return &ang[i*xtal::rotLen(rot)];} + + //@brief : get the stored rotations + //@return: pointer to rotations + Real const * getAngles() const {return ang.data();} + + //@brief : read angle angle file + //@param is: input stream to read from + void read(std::istream& is); + + //@brief : write an angle file + //@param os: output stream to write to + void write(std::ostream& os) const; + + //@brief : read angle angle file + //@param name: name of file to read from + void read(const char * name); + void read(std::string& name) {read(name.c_str());} + + //@brief : write an angle file + //@param name: name of file to write to + void write(const char * name) const; + void write(std::string& name) const {write(name.c_str());} + + //@brief: create an empty angle file + AngleFile() {} + + //@brief : build an AngleFile from an input + //@param name: name of file to parse + AngleFile(const char* name) {read(name);} + AngleFile(std::string name) {read(name);} + + private: + static_assert(std::is_floating_point::value, "AngleFile must be templated on a floating point type"); + std::vector ang;//actual orientations + xtal::Rotation rot;//orientation representation type + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// Implementation Details // +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +namespace emsoft { + namespace detail { + //a custom local that treats commas as white space + //modified from https://stackoverflow.com/questions/1894886/parsing-a-comma-delimited-stdstring + struct csv_reader: std::ctype { + csv_reader(): std::ctype(get_table()) {} + + static std::ctype_base::mask const* get_table() { + static std::ctype_base::mask const * const pMask = std::ctype::classic_table();//get the default type table + static std::vector rc(pMask, pMask + std::ctype::table_size);//copy the table + rc[','] = std::ctype_base::space;//treat comma as white space + return rc.data(); + } + }; + } + + //@brief : read angle angle file + //@param is: input stream to read from + template + void AngleFile::read(std::istream& is) { + //try to get name and number + size_t num = 0; + if(!(is >> rot >> num)) throw std::runtime_error("failed to read name and number of orientations from angle file"); + if(xtal::Rotation::Unknown == rot) throw std::runtime_error("first line of angle file must be one of 'eu', 'om', 'ax', 'ro', 'qu', 'ho', or 'cu'"); + num *= xtal::rotLen(rot); + + //read all available numbers into the angle vector + ang.reserve(num * xtal::rotLen(rot));//alloate space up front + is.imbue(std::locale(std::locale(), new detail::csv_reader()));//treat commas as white space + std::copy(std::istream_iterator(is), std::istream_iterator(), std::back_inserter(ang));//this one liner only w + + //make sure the size is what we expected + if(ang.size() < num) throw std::runtime_error("not enough orientions in angle file"); + if(ang.size() > num) throw std::runtime_error("too many orientions in angle file"); + + //convert from degrees to radians if needed + if(xtal::Rotation::Euler == rot) std::for_each(ang.begin(), ang.end(), [](Real& v){v *= xtal::Constants::dg2rd;}); + } + + //@brief : write an angle file + //@param os: output stream to write to + template + void AngleFile::write(std::ostream& os) const { + //write type/number + os << rot << '\n' << getNum() << '\n'; + + //write orientations + const size_t len = xtal::rotLen(rot); + const size_t num = getNum(); + Real const * p = ang.data(); + if(xtal::Rotation::Euler == rot) {//euler angles (need to convert from radians to degree) + for(size_t i = 0; i < num; i++) { + os << p[0] * xtal::Constants::rd2dg; + for(size_t j = 1; j < len; j++) os << ',' << p[j] * xtal::Constants::rd2dg; + os << '\n'; + p += len; + } + } else {//normal representatino + for(size_t i = 0; i < num; i++) { + os << p[0]; + for(size_t j = 1; j < len; j++) os << ',' << p[j]; + os << '\n'; + p += len; + } + } + } + + //@brief : read angle angle file + //@param name: name of file to read from + template + void AngleFile::read(const char * name) { + std::ifstream is(name); + read(is); + } + + //@brief : write an angle file + //@param name: name of file to write to + template + void AngleFile::write(const char * name) const { + std::ofstream os(name); + write(os); + } +} + +#endif//_emsoft_h_ diff --git a/include/xtal/vendor/hkl.hpp b/include/xtal/vendor/hkl.hpp new file mode 100644 index 0000000..8ce4900 --- /dev/null +++ b/include/xtal/vendor/hkl.hpp @@ -0,0 +1,552 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019, William C. Lenthe * + * All rights reserved. * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _HKL_H_ +#define _HKL_H_ + +#include +#include + +namespace hkl { + struct Phase { + float lat[6];//a, b, c, alpha, beta, gamma (in angstroms, degrees) + std::string name ; + int32_t laue ;//laue group number + int32_t space ;//space group number + }; + + //@brief : get the type of a file + //@param fileName: name to parse extension from + //@return : parsed extension + enum class FileType {Unknown, Ctf, Hdf}; //enumeration of file types + FileType getFileType(std::string fileName); + + struct OrientationMap { + std::string project ;//project name + std::string author ;//author name + std::string jobMode ;//job mode + int32_t xCells, yCells, zCells;//number of voxels in each direction + float xStep , yStep , zStep ;//voxel resolution in microns + float acqE1 , acqE2 , acqE3 ; + std::string euStr ;//euler angle string + float mag ;//magnification + float cover ;//coverage + float dev ;//device + float kv ;//accelerating voltage + float angle ;//tilt angle + float axis ;//tilt axis + std::vector phaseList ;//list of phases + + std::vector phase ;//phase + std::vector x, y ;//x/y coordinate of pixel in microns + std::vector bands ;//bands + std::vector err ;//error measurement + std::vector eu ;//euler angles + std::vector mad ;//MAD + std::vector bc ;//BC + std::vector bs ;//BS + + //@brief: construct an empty orientation map + OrientationMap(): zCells(1) {} + + //@brief : construct an orientation map from a file + //@param fileName: file to read (currently only .ctf is supported) + OrientationMap(std::string fileName) {read(fileName);} + + //@brief : check if a file can be ready by this class (based on file extension) + //@return: true/false if the file type can/cannot be read + static bool CanRead(std::string fileName); + + //@brief : allocate space to hold scan data based on grid type and dimensions + //@param cols: bitmask of columns to allocate + enum ColumnType : uint16_t { + CTF_Phase = 0x0001, + CTF_X = 0x0002, + CTF_Y = 0x0004, + CTF_Bands = 0x0008, + CTF_Error = 0x0010, + CTF_Euler1 = 0x0020, + CTF_Euler2 = 0x0040, + CTF_Euler3 = 0x0080, + CTF_MAD = 0x0100, + CTF_BC = 0x0200, + CTF_BS = 0x0400, + CTF_ALL = 0x07FF,//all field + CTF_Euler = 0x00E0,//CTF_Euler1 | CTF_Euler2 | CTF_Euler3 + CTF_Min = 0x00E1 //minimal value for a valid CTF file = CTF_Phase | CTF_Euler + }; + void allocate(const uint16_t cols); + + //@brief : read scan data from an HKL orientation map file + //@param fileName: file to read (currently only .ctf is supported) + void read(std::string fileName); + + //@brief : write scan data to an HKL orientation map file + //@param fileName: file to write (currently only .ctf is supported) + void write(std::string fileName); + + private: + //@brief : read data from a '.ctf' file + //@param fileName: name of ctf file to read + //@return : number of scan points read from file + void readCtf(std::string fileName); + + //@brief : read an ctf header and parse the values + //@param is: input stream to read the header from + //@return : colunns detected + std::vector readCtfHeader(std::istream& is); + + //@brief : read ctf data using an input stream + //@param is : input stream set data start + //@param cols: column headers + //@return : number of points (rows) parsed + size_t readCtfData(std::istream& is, const std::vector& cols); + + //@brief : read ctf data using a memory map + //@param fileName: name of file to read + //@param offset : offset to data start (in bytes) + //@param cols : column headers + //@return : number of points (rows) parsed + // size_t readCtfDataMemMap(std::string fileName, std::streamoff offset, const std::vector& cols); + + //@brief : write data to a '.ctf' file + //@param fileName: name of ctf file to write + void writeCtf(std::string fileName); + + //@brief : write ctf header + //@param os: output stream to write the header to + void writeCtfHeader(std::ostream& os); + + //@brief : write ctf data + //@param os : output stream to write data to + void writeCtfData(std::ostream& os); + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// Implementation Details // +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +namespace hkl { + //@brief : get the type of a file + //@param fileName: name to parse extension from + //@return : parsed extension + FileType getFileType(std::string fileName) { + size_t pos = fileName.find_last_of(".");//find the last '.' in the name + if(std::string::npos == pos) return FileType::Unknown;//handle files with no extension + std::string ext = fileName.substr(pos+1);//extract the file extension + std::transform(ext.begin(), ext.end(), ext.begin(), [](char c){return std::tolower(c);});//convert to lowercase + + if (0 == ext.compare("ctf" )) return FileType::Ctf; + else if(0 == ext.compare("hdf" )) return FileType::Hdf; + else if(0 == ext.compare("hdf5")) return FileType::Hdf; + else if(0 == ext.compare("h5" )) return FileType::Hdf; + else return FileType::Unknown; + } + + //@brief : check if a file can be ready by this class (based on file extension) + //@return: true/false if the file type can/cannot be read + bool OrientationMap::CanRead(std::string fileName) { + switch(getFileType(fileName)) { + case FileType::Ctf : return true ; + case FileType::Hdf : return false; + case FileType::Unknown: return false; + } + return false; + } + + //@brief : allocate space to hold scan data based on grid type and dimensions + //@param cols: bitmask of columns to allocate + void OrientationMap::allocate(const uint16_t cols) { + //compute number of pixels based on dimensions and grid type + size_t totalPoints = xCells * yCells; + if(1 != zCells) throw std::runtime_error("only 2D scans are currently supported"); + + if(!(cols & CTF_Min)) throw std::runtime_error("ctf must have at least phase + orientation"); + + //allocate arrays (filling new space with zero) + const bool hasEu = (cols & CTF_Euler1) || (cols & CTF_Euler2) || (cols & CTF_Euler3); + if(hasEu && !(cols & CTF_Euler)) throw std::runtime_error("cannot parse partial euler angles"); + if(cols & CTF_Phase) phase.resize( totalPoints); + if(cols & CTF_X ) x .resize( totalPoints); + if(cols & CTF_Y ) y .resize( totalPoints); + if(cols & CTF_Bands) bands.resize( totalPoints); + if(cols & CTF_Error) err .resize( totalPoints); + if(cols & CTF_Euler) eu .resize(3*totalPoints); + if(cols & CTF_MAD ) mad .resize( totalPoints); + if(cols & CTF_BC ) bc .resize( totalPoints); + if(cols & CTF_BS ) bs .resize( totalPoints); + } + + //@brief : read scan data from an HKL orientation map file + //@param fileName: file to read (currently only .ctf is supported) + void OrientationMap::read(std::string fileName) { + //read data from the file + switch(getFileType(fileName)) {//dispatch the file to the appropraite reader based on the extension + case FileType::Ctf: readCtf(fileName); break; + default: std::runtime_error("unsupported file type (currently only .ctf files are supported)"); + } + } + + //@brief : write scan data to an HKL orientation map file + //@param fileName: file to write (currently only .ctf is supported) + void OrientationMap::write(std::string fileName) { + switch(getFileType(fileName)) {//dispatch the file to the appropraite writer based on the extension + case FileType::Ctf: writeCtf(fileName); return; + default: throw std::runtime_error("unsupported file type (currently only .ctf files are supported)"); + } + } + + //@brief : read data from a '.ctf' file + //@param fileName: name of ctf file to read + //@return : number of scan points read from file + void OrientationMap::readCtf(std::string fileName) { + //parse the header + std::ifstream is(fileName.c_str());//open file + if(!is) throw std::runtime_error("ctf file " + fileName + " doesn't exist"); + std::vector cols = readCtfHeader(is);//read header and count number of tokens per point + if(0 != jobMode.compare("Grid")) throw std::runtime_error("only grid job mode is supported (got '" + jobMode + "')"); + + //allocate space + uint16_t c = 0; + for(const uint16_t& i : cols) c |= i; + allocate(c); + //read the data + size_t pointsRead = 0;//nothing has been read + static const bool UseMemMap = false; + if(UseMemMap) { + std::streamoff offset = is.tellg();//get offset to data start + is.close();//close the file + throw std::runtime_error("not yet implemented"); + } else { + pointsRead = readCtfData(is, cols); + } + + //check that enough data was read + if(pointsRead < phase.size()) {//I'll compare against phase since it is always present (and easier than comparing against euler angles) + std::stringstream ss; + ss << "file ended after reading " << pointsRead << " of " << phase.size() << " data points"; + throw std::runtime_error(ss.str()); + } + } + + //@brief : read an ctf header and parse the values + //@param is: input stream to read the header from + //@return : colunns detected + std::vector OrientationMap::readCtfHeader(std::istream& is) { + std::string line; + std::string token; + bool readProject = false, readAuthor = false, readJobMode = false; + bool readXCells = false, readYCells = false, readZCells = false; + bool readXStep = false, readYStep = false, readZStep = false; + bool readacqE1 = false, readacqE2 = false, readacqE3 = false; + bool readEulerLine = false; + bool readMag = false, readCoverage = false, readDevice = false; + bool readKv = false, readTiltAngle = false, readTiltAxis = false; + std::vector cols; + + //check for magic string + std::getline(is, line);//extract first line from file + if(0 != std::string(line).compare(0, 17, "Channel Text File")) throw std::runtime_error("not a valid ctf file (bad header)");//need to compare this way to handle \r\n from windows files on a mac + + //parse header + while(cols.empty()) { + //extract entire line from file and copy to stringstream + if(!std::getline(is, line)) throw std::runtime_error("not a valid ctf file (no column headers)");//if we failed to extract a line we're in trouble + std::istringstream iss(line); + + //get the key word if the line isn't blank + if(iss >> token) { + //get value for appropriate key + if(0 == token.compare("Phase" ) || // check for column headers first so we find Euler(1/2/3) before "Euler angles refer to..." + 0 == token.compare("X" ) || + 0 == token.compare("Y" ) || + 0 == token.compare("Bands" ) || + 0 == token.compare("Error" ) || + 0 == token.compare("Euler1") || + 0 == token.compare("Euler2") || + 0 == token.compare("Euler3") || + 0 == token.compare("MAD" ) || + 0 == token.compare("BC" ) || + 0 == token.compare("BS" ) ) { + //parse the list of tokens + bool euPresent[3] = {false, false, false}; + do { + if (0 == token.compare("Phase" )) cols.push_back(CTF_Phase ); + else if(0 == token.compare("X" )) cols.push_back(CTF_X ); + else if(0 == token.compare("Y" )) cols.push_back(CTF_Y ); + else if(0 == token.compare("Bands" )) cols.push_back(CTF_Bands ); + else if(0 == token.compare("Error" )) cols.push_back(CTF_Error ); + else if(0 == token.compare("Euler1")){cols.push_back(CTF_Euler1); euPresent[0] = true;} + else if(0 == token.compare("Euler2")){cols.push_back(CTF_Euler2); euPresent[1] = true;} + else if(0 == token.compare("Euler3")){cols.push_back(CTF_Euler3); euPresent[2] = true;} + else if(0 == token.compare("MAD" )) cols.push_back(CTF_MAD ); + else if(0 == token.compare("BC" )) cols.push_back(CTF_BC ); + else if(0 == token.compare("BS" )) cols.push_back(CTF_BS ); + else throw std::runtime_error("unknown ctf column header: " + token); + } while(iss >> token); + bool allEulers = euPresent[0] && euPresent[1] && euPresent[2]; + bool anyEulers = euPresent[0] || euPresent[1] || euPresent[2]; + if(anyEulers && !allEulers) throw std::runtime_error("only some euler angles present in column headers"); + } else if(0 == token.compare("Prj" )) { std::getline(iss >> std::ws, project); readProject = true; + } else if(0 == token.compare("Author" )) { std::getline(iss >> std::ws, author ); readAuthor = true; + } else if(0 == token.compare("JobMode" )) { std::getline(iss >> std::ws, jobMode); readJobMode = true; + } else if(0 == token.compare("XCells" )) { iss >> xCells ; readXCells = true; + } else if(0 == token.compare("YCells" )) { iss >> yCells ; readYCells = true; + } else if(0 == token.compare("ZCells" )) { iss >> zCells ; readZCells = true; + } else if(0 == token.compare("XStep" )) { iss >> xStep ; readXStep = true; + } else if(0 == token.compare("YStep" )) { iss >> yStep ; readYStep = true; + } else if(0 == token.compare("ZStep" )) { iss >> zStep ; readZStep = true; + } else if(0 == token.compare("AcqE1" )) { iss >> acqE1 ; readacqE1 = true; + } else if(0 == token.compare("AcqE2" )) { iss >> acqE1 ; readacqE2 = true; + } else if(0 == token.compare("AcqE3" )) { iss >> acqE2 ; readacqE3 = true; + } else if(0 == token.compare("Euler" )) { + euStr = std::string(line); + readEulerLine = true; + while(iss >> token) { + if (0 == token.compare("Mag" )) { iss >> mag ; readMag = true; + } else if(0 == token.compare("Coverage" )) { iss >> cover ; readCoverage = true; + } else if(0 == token.compare("Device" )) { iss >> dev ; readDevice = true; + } else if(0 == token.compare("KV" )) { iss >> kv ; readKv = true; + } else if(0 == token.compare("TiltAngle")) { iss >> angle ; readTiltAngle = true; + } else if(0 == token.compare("TiltAxis" )) { iss >> axis ; readTiltAxis = true; + } + } + } else if(0 == token.compare("Phases" )) { + size_t numPhases; + iss >> numPhases; + for(size_t i = 0; i < numPhases; i++) { + //phase lines are formatted as 'a;b;c\talpha;beta;gamma\tname\tlaue_group\tspacegroup' + std::getline(is, line);//extract entire line from file + std::replace(line.begin(), line.end(), ';', '\t');//convert to fully tab delimited to make things easier + iss.str(line);//change istream buffer + iss.clear();//clear any flags from istream + + //sanity check (phase lines should start with a digit) + iss >> std::skipws; + if(!std::isdigit(iss.peek())) throw std::runtime_error("end of ctf phases reached before epxected number of phases was read"); + + //parse phase + Phase p; + iss >> p.lat[0] >> p.lat[1] >> p.lat[2];//a, b, c + iss >> p.lat[3] >> p.lat[4] >> p.lat[5];//alpha, beta, gamma + iss >> p.name >> p.laue >> p.space;///name, laue number, space group number + phaseList.push_back(std::move(p)); + } + } else throw std::runtime_error("unknown ctf header keyword '" + token + "'"); + } + } + + //make sure all required parameters were read + if(!readXCells || !readYCells) throw std::runtime_error("missing ctf dimensions"); + if(!readXStep || !readYStep ) throw std::runtime_error("missing ctf resolution"); + if(!readacqE1 || !readacqE2 || !readacqE3 ) throw std::runtime_error("missing ctf header AcqE values"); + if(!readEulerLine) throw std::runtime_error("missing ctf euler angle convention line"); + if(!readMag ) throw std::runtime_error("missing ctf magnification" ); + if(!readCoverage ) throw std::runtime_error("missing ctf coverage" ); + if(!readDevice ) throw std::runtime_error("missing ctf device" ); + if(!readKv ) throw std::runtime_error("missing ctf accelerating voltage" ); + if(!readTiltAngle) throw std::runtime_error("missing ctf tilt angle" ); + if(!readTiltAxis ) throw std::runtime_error("missing ctf tilt axis" ); + + //handle missing optional values and return column headers + if(!readZCells) zCells = 1; + return cols; + } + + //@brief : read ctf data using an input stream + //@param is : input stream set data start + //@param cols: column headers + //@return : number of points (rows) parsed + size_t OrientationMap::readCtfData(std::istream& is, const std::vector& cols) { + //check if the columns are in the normal layout + const uint16_t defaultLayout[11] = { + CTF_Phase , + CTF_X , + CTF_Y , + CTF_Bands , + CTF_Error , + CTF_Euler1, + CTF_Euler2, + CTF_Euler3, + CTF_MAD , + CTF_BC , + CTF_BS , + }; + const bool isDefault = std::equal(cols.cbegin(), cols.cend(), defaultLayout); + + char line[512]; + const size_t totalPoints = xCells * yCells * zCells; + + if(isDefault) { + for(size_t i = 0; i < totalPoints; i++) { + char* data = line; + if(!is.getline(line, sizeof(line))) return i;//get next line + phase[i ] = (uint8_t) std::strtoul(data, &data, 10); + x [i ] = std::strtof (data, &data ); + y [i ] = std::strtof (data, &data ); + bands[i ] = (uint8_t) std::strtoul(data, &data, 10); + err [i ] = std::strtof (data, &data ); + eu [3*i ] = std::strtof (data, &data ); + eu [3*i+1] = std::strtof (data, &data ); + eu [3*i+2] = std::strtof (data, &data ); + mad [i ] = std::strtof (data, &data ); + bc [i ] = std::strtof (data, &data ); + bs [i ] = std::strtof (data, &data ); + } + } else { + //get a void pointer to each item in order and compute strides + std::vector strides ;//size of data type in each column + std::vector pointers;//pointers to start of data in each column + std::vector isInt ;//1/0 if each column is int/float + for(const uint16_t& c : cols) { + switch(c) { + case CTF_Phase : strides.push_back(sizeof(phase.front())); pointers.push_back((char*)phase .data() ); isInt.push_back(1); break; + case CTF_X : strides.push_back(sizeof(x .front())); pointers.push_back((char*)x .data() ); isInt.push_back(0); break; + case CTF_Y : strides.push_back(sizeof(y .front())); pointers.push_back((char*)y .data() ); isInt.push_back(0); break; + case CTF_Bands : strides.push_back(sizeof(bands.front())); pointers.push_back((char*)bands .data() ); isInt.push_back(1); break; + case CTF_Error : strides.push_back(sizeof(err .front())); pointers.push_back((char*)err .data() ); isInt.push_back(0); break; + case CTF_Euler1: strides.push_back(sizeof(eu .front())); pointers.push_back((char*)eu .data() ); isInt.push_back(0); break; + case CTF_Euler2: strides.push_back(sizeof(eu .front())); pointers.push_back((char*)eu .data() + sizeof(eu.front()) ); isInt.push_back(0); break; + case CTF_Euler3: strides.push_back(sizeof(eu .front())); pointers.push_back((char*)eu .data() + sizeof(eu.front()) * 2); isInt.push_back(0); break; + case CTF_MAD : strides.push_back(sizeof(mad .front())); pointers.push_back((char*)mad .data() ); isInt.push_back(0); break; + case CTF_BC : strides.push_back(sizeof(bc .front())); pointers.push_back((char*)bc .data() ); isInt.push_back(0); break; + case CTF_BS : strides.push_back(sizeof(bs .front())); pointers.push_back((char*)bs .data() ); isInt.push_back(0); break; + default: throw std::runtime_error("unknown column type"); + } + } + + //parse data + for(size_t i = 0; i < totalPoints; i++) { + char* data = line; + if(!is.getline(line, sizeof(line))) return i;//get next line + for(size_t j = 0; j < strides.size(); j++) {//loop over columns + char* pData = pointers[j];//get pointer to column data + if(isInt[j])//parse + reinterpret_cast(pData)[i] = (uint8_t)std::strtoul(data, &data, 10); + else + reinterpret_cast(pData)[i] = std::strtof (data, &data ); + pointers[j] += strides[j];//increment pointer + } + } + } + return totalPoints; + } + + //@brief : write data to a '.ctf' file + //@param fileName: name of ctf file to write + void OrientationMap::writeCtf(std::string fileName) { + std::ofstream os(fileName.c_str());//open file + writeCtfHeader(os);//write data + writeCtfData(os);//write data + } + + //@brief : write ctf header + //@param os: output stream to write the header to + void OrientationMap::writeCtfHeader(std::ostream& os) { + os << "Channel Text File\n"; + os << "Prj " << project << '\n'; + os << "Author\t" << author << '\n'; + os << "JobMode\t" << jobMode << '\n'; + os << "XCells\t" << xCells << '\n'; + os << "YCells\t" << yCells << '\n'; + if(zCells != 1) os << "ZCells\t" << zCells << '\n'; + os << "XStep\t" << xStep << '\n'; + os << "YStep\t" << yStep << '\n'; + if(zCells != 1) os << "ZStep\t" << zStep << '\n'; + os << "AcqE1\t" << acqE1 << '\n'; + os << "AcqE2\t" << acqE2 << '\n'; + os << "AcqE3\t" << acqE3 << '\n'; + os << "Euler angles refer to Sample Coordinate system (CS0)! "; + os << "Mag\t" << mag << ' '; + os << "Coverage\t" << cover << ' '; + os << "Device\t" << dev << ' '; + os << "KV\t" << kv << ' '; + os << "TiltAngle\t" << angle << ' '; + os << "TiltAxis\t" << axis << '\n'; + os << "Phases\t" << phaseList.size() << '\n'; + for(const Phase& p : phaseList) { + os << p.lat[0] << ';' << p.lat[1] << ';' << p.lat[2] << '\t'; + os << p.lat[3] << ';' << p.lat[4] << ';' << p.lat[5] << '\t'; + os << p.name << '\t' << p.laue << '\t' << p.space << '\n'; + } + os << "Phase\t" ; + if(!x .empty()) os << "X\t" ; + if(!y .empty()) os << "Y\t" ; + if(!bands.empty()) os << "Bands\t" ; + if(!err .empty()) os << "Error\t" ; + os << "Euler1\t"; + os << "Euler2\t"; + os << "Euler3\t"; + if(!mad .empty()) os << "MAD\t" ; + if(!bc .empty()) os << "BC\t" ; + if(!bs .empty()) os << "BS" ; + os << '\n'; + } + + //@brief : write ctf data + //@param os : output stream to write data to + void OrientationMap::writeCtfData(std::ostream& os) { + os << std::fixed; + for(size_t i = 0; i < phase.size(); i++) { + os << (int) phase [i] << '\t'; + os << std::setprecision(3); + if(!x .empty()) os << x [i] << '\t'; + if(!y .empty()) os << y [i] << '\t'; + if(!bands.empty()) os << (int) bands [i] << '\t'; + os << std::setprecision(6); + if(!err .empty()) os << err [i] << '\t'; + os << std::setprecision(3); + os << eu[3*i ] << '\t'; + os << eu[3*i+1] << '\t'; + os << eu[3*i+2] << '\t'; + os << std::setprecision(0); + if(!mad .empty()) os << mad [i] << '\t'; + if(!bc .empty()) os << bc [i] << '\t'; + if(!bs .empty()) os << bs [i]; + os << '\n'; + } + } +} + + + +/* + //convert from degrees to radians and 1->0 phase indexing + const T factor = M_PI / 180.0; + std::for_each(eu.begin(), eu.end(), [factor](T& e){e *= factor;}); + std::for_each(phase.begin(), phase.end(), [](size_t& i){--i;}); + + //correct for euler convention + + T* pEulers = eu.data(); + const T pi = T(2) * std::acos(T(0)); + const T twoPi = T(2) * std::acos(T(0)); + for(size_t i = 0; i < eu.size() / 3; i++) + pEulers[3*i] = std::fmod(pEulers[3*i]+pi, twoPi); + */ + + +#endif//_HKL_H_ diff --git a/include/xtal/vendor/tsl.hpp b/include/xtal/vendor/tsl.hpp new file mode 100644 index 0000000..9e40f44 --- /dev/null +++ b/include/xtal/vendor/tsl.hpp @@ -0,0 +1,832 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019, William C. Lenthe * + * All rights reserved. * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _tsl_h_ +#define _tsl_h_ + +#include +#include +#include + +#ifdef XTAL_USE_H5 + #include "H5Cpp.h" +#endif + +namespace tsl { + struct HKLFamily { + int32_t h,k,l ;//hkl plane + float intensity;//diffraction intensity + int8_t useIdx ;//use in indexing + int8_t showBands;//overlay bands on indexed patterns + }; + + struct Phase { + size_t num ;//phase number + std::string name ;//material name + std::string form ;//chemical formula + std::string info ;//additional information + int32_t sym ;//tsl symmetry number + float lat[6];//lattice constants (a, b, c, alpha, beta, gamma in angstroms, degrees) + std::vector hklFam;//hkl families + float el[36];//elastic constants (6x6 matrix in row major order) + std::vector cats ;//categories (not sure what this is...) + }; + + enum class GridType {Unknown, Square, Hexagonal};//enumeration of grid types + + //@brief : read a grid type from an input stream + //@param is : stream to read from + //@param grid: grid type to read into + //@return : stream read from + std::istream& operator>>(std::istream& is, GridType& grid); + + //@brief : write a grid to an output stream + //@param os : stream to write to + //@param grid: grid type to write from + //@return : stream written to + std::ostream& operator<<(std::ostream& os, const GridType& grid); + + //@brief : get the type of a file + //@param fileName: name to parse extension from + //@return : parsed extension + enum class FileType {Unknown, Ang, Osc, Hdf}; //enumeration of file types + FileType getFileType(std::string fileName); + + struct OrientationMap { + //header information + float pixPerUm ; + float xStar, yStar, zStar ;//pattern center calibration + float sampTlt, camTlt ;//sample/camera tilt in degrees (only in h5 files) + float workingDistance ;//working distance in mm + float xStep , yStep ;//pixel size in microns + int32_t nColsOdd, nColsEven ;//width in pixels (same for square grid, alternating rows for hex grid) + int32_t nRows ;//height in pixels + std::string operatorName ;//operator name + std::string sampleId ;//same ID string + std::string scanId ;//scan ID string + GridType gridType ;//square/hex grid + std::vector phaseList ;//list of indexed phases (the 'phase' scan data indexes into this array) + + //scan data (all in row major order) + std::vector eu ;//euler angle triples for each pixel + std::vector x, y ;//x/y coordinate of pixel in microns + std::vector iq ;//image quality + std::vector ci ;//confidence index + std::vector sem ;//secondary electron signal + std::vector fit ;//fit + std::vector phase ;//phase ID of each pixel (indexes into phaseList) + + //@brief: construct an empty orientation map + OrientationMap() : gridType(GridType::Unknown) {} + + //@brief : construct an orientation map from a file + //@param fileName: file to read + //@param aux : auxilary information (for hdf5 datasets this is the path to the dataset) + OrientationMap(std::string fileName, const std::string aux = "") : gridType(GridType::Unknown) {read(fileName, aux);} + + //@brief : check if a file can be ready by this class (based on file extension) + //@return: true/false if the file type can/cannot be read + static bool CanRead(std::string fileName); + + //@brief : allocate space to hold scan data based on grid type and dimensions + //@param tokenCount: number of arrays to use - eu, x, y, iq, ci and phase are always allocated, sem is only allocated for 9+ tokens and fit for 10+ + void allocate(const size_t tokenCount); + + //@brief : read scan data from a TSL orientation map file + //@param fileName: file to read (currently only .ang is supported) + //@param aux : auxilary information (for hdf5 datasets this is the path to the dataset) + void read(std::string fileName, const std::string aux = ""); + + //@brief : write scan data to a TSL orientation map file + //@param fileName: file to write (currently only .ang is supported) + void write(std::string fileName); + +#ifdef XTAL_USE_H5 + //@brief : read data from a '.h5' file + //@param grp: folder to read data from + void readH5(H5::Group grp); +#endif + + private: + //@brief : read data from a '.ang' file + //@param fileName: name of ang file to read + //@return : number of scan points read from file + void readAng(std::string fileName); + + //@brief : read an ang header and parse the values + //@param is: input stream to read the header from + //@return : number of tokens (number of data columns) + size_t readAngHeader(std::istream& is); + + //@brief : read ang data using an input stream + //@param is : input stream set data start + //@param tokens: number of tokens per point + //@return : number of points (rows) parsed + size_t readAngData(std::istream& is, size_t tokens); + + //@brief : write data to an '.ang' file + //@param fileName: name of ang file to write + void writeAng(std::string fileName); + + //@brief : write ang header + //@param os: output stream to write the header to + void writeAngHeader(std::ostream& os); + + //@brief : write ang data + //@param os : output stream to write data to + void writeAngData(std::ostream& os); + }; + +} + +//////////////////////////////////////////////////////////////////////////////// +// Implementation Details // +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace tsl { + + //@brief : read a grid type from an input stream + //@param is : stream to read from + //@param grid: grid type to read into + //@return : stream read from + std::istream& operator>>(std::istream& is, GridType& grid) { + std::string name; + is >> name; + if (0 == name.compare("SqrGrid")) grid = GridType::Square; + else if(0 == name.compare("HexGrid")) grid = GridType::Hexagonal; + else grid = GridType::Unknown; + return is; + } + + //@brief : write a grid to an output stream + //@param os : stream to write to + //@param grid: grid type to write from + //@return : stream written to + std::ostream& operator<<(std::ostream& os, const GridType& grid) { + switch(grid) { + case GridType::Square : return os << "SqrGrid"; + case GridType::Hexagonal: return os << "HexGrid"; + default: throw std::runtime_error("unknown grid type"); + } + } + + //@brief : get the type of a file + //@param fileName: name to parse extension from + //@return : parsed extension + FileType getFileType(std::string fileName) { + size_t pos = fileName.find_last_of(".");//find the last '.' in the name + if(std::string::npos == pos) return FileType::Unknown;//handle files with no extension + std::string ext = fileName.substr(pos+1);//extract the file extension + std::transform(ext.begin(), ext.end(), ext.begin(), [](char c){return std::tolower(c);});//convert to lowercase + + if (0 == ext.compare("ang" )) return FileType::Ang; + else if(0 == ext.compare("osc" )) return FileType::Osc; + else if(0 == ext.compare("hdf" )) return FileType::Hdf; + else if(0 == ext.compare("hdf5")) return FileType::Hdf; + else if(0 == ext.compare("h5" )) return FileType::Hdf; + else return FileType::Unknown; + } + + //@brief : check if a file can be ready by this class (based on file extension) + //@return: true/false if the file type can/cannot be read + bool OrientationMap::CanRead(std::string fileName) { + switch(getFileType(fileName)) { + case FileType::Ang : return true ; + #ifdef XTAL_USE_H5 + case FileType::Hdf : return true ; + #else + case FileType::Hdf : return false; + #endif + case FileType::Osc : return false; + case FileType::Unknown: return false; + } + return false; + } + + //@brief : allocate space to hold scan data based on grid type and dimensions + //@param tokenCount: number of arrays to use - eu, x, y, iq, ci and phase are always allocated, sem is only allocated for 9+ tokens and fit for 10+ + void OrientationMap::allocate(const size_t tokenCount) { + //compute number of pixels based on dimensions and grid type + size_t totalPoints = 0; + switch(gridType) { + case GridType::Square: { + totalPoints = std::max(nColsOdd, nColsEven) * nRows; + } break; + + case GridType::Hexagonal: { + totalPoints = size_t(nRows / 2) * (nColsOdd + nColsEven); + if(1 == nRows % 2) totalPoints += nColsOdd; + } break; + + default: throw std::runtime_error("only Square and Hexagonal grid types are supported"); + } + + //allocate arrays (filling new space with zero) + eu .resize(3*totalPoints); + x .resize( totalPoints); + y .resize( totalPoints); + iq .resize( totalPoints); + ci .resize( totalPoints); + phase.resize( totalPoints); + if(tokenCount > 8) sem.resize(totalPoints); + if(tokenCount > 9) fit.resize(totalPoints); + } + + //@brief : read scan data from a TSL orientation map file + //@param fileName: file to read (currently only .ang is supported) + //@param aux : auxilary information (for hdf5 datasets this is the path to the dataset) + void OrientationMap::read(std::string fileName, const std::string aux) { + //read data from the file + switch(getFileType(fileName)) {//dispatch the file to the appropraite reader based on the extension + case FileType::Ang: readAng(fileName); break; + #ifdef XTAL_USE_H5 + case FileType::Hdf: { + H5::H5File file(fileName.c_str(), H5F_ACC_RDONLY); + readH5(file.openGroup(aux)); + } + #endif + default: std::runtime_error("unsupported file type (currently only .ang files are supported)"); + } + } + + //@brief : write scan data to a TSL orientation map file + //@param fileName: file to write (currently only .ang is supported) + void OrientationMap::write(std::string fileName) { + switch(getFileType(fileName)) {//dispatch the file to the appropraite writer based on the extension + case FileType::Ang: writeAng(fileName); return; + default: throw std::runtime_error("unsupported file type (currently only .ang files are supported)"); + } + } + + //@brief : read data from a '.ang' file + //@param fileName: name of ang file to read + //@return : number of scan points read from file + void OrientationMap::readAng(std::string fileName) { + //parse the header + std::ifstream is(fileName.c_str());//open file + if(!is) throw std::runtime_error("ang file " + fileName + " doesn't exist"); + size_t tokenCount = readAngHeader(is);//read header and count number of tokens per point + allocate(tokenCount);//allocate space + + //read the data + const size_t pointsRead = readAngData(is, tokenCount); + + //check that enough data was read + if(pointsRead < iq.size()) {//I'll compare against IQ since it is always present (and easier than comparing against euler angles) + std::stringstream ss; + ss << "file ended after reading " << pointsRead << " of " << iq.size() << " data points"; + throw std::runtime_error(ss.str()); + } + } + + //@brief : read an ang header and parse the values + //@param is: input stream to read the header from + //@return : number of tokens (number of data columns) + size_t OrientationMap::readAngHeader(std::istream& is) { + //flags for which header tokens have been parsed + bool readPixPerUm = false; + bool readXStar = false, readYStar = false, readZStar = false; + bool readWorkingDistance = false; + bool readXStep = false, readYStep = false; + bool readColsOdd = false, readColsEven = false; + bool readRows = false; + bool readOperatorName = false; + bool readSampleId = false; + bool readScanId = false; + bool readGridType = false; + + //flags for which phase subheader tokens have been parsed (set to true values in case there are no phases listed) + bool readPhaseSymmetry = true; + size_t targetFamilies = 0 ; + bool readPhaseMaterial = true; + bool readPhaseFormula = true; + bool readPhaseInfo = true; + bool readPhaseLattice = true; + bool readPhaseHkl = true; + size_t phaseElasticCount = 6 ; + bool readPhaseCategories = true; + + //initialize values that don't exist in angs + sampTlt = camTlt = NAN; + + //now start parsing the header + std::string line;//buffer to hold single line of header + std::string token;//current header token + while('#' == is.peek()) {//all header lines start with #, keep going until we get to data + std::getline(is, line);//extract entire line from file + std::istringstream iss(line.data()+1);//skip the '#' + if(iss >> token) {//get the key word if the line isn't blank + //get value for appropriate key + if (0 == token.compare("TEM_PIXperUM" )) {iss >> pixPerUm ; readPixPerUm = true; + } else if(0 == token.compare("x-star" )) {iss >> xStar ; readXStar = true; + } else if(0 == token.compare("y-star" )) {iss >> yStar ; readYStar = true; + } else if(0 == token.compare("z-star" )) {iss >> zStar ; readZStar = true; + + //these are only in relatively new angs + } else if(0 == token.compare("SampleTiltAngle" )) {iss >> sampTlt ; + } else if(0 == token.compare("CameraElevationAngle")) {iss >> camTlt ; + } else if(0 == token.compare("CameraAzimuthalAngle")) { + } else if(0 == token.compare("PointGroupID" )) { + + } else if(0 == token.compare("WorkingDistance")) {iss >> workingDistance; readWorkingDistance = true; + } else if(0 == token.compare("GRID:" )) {iss >> gridType ; readGridType = true; + } else if(0 == token.compare("XSTEP:" )) {iss >> xStep ; readXStep = true; + } else if(0 == token.compare("YSTEP:" )) {iss >> yStep ; readYStep = true; + } else if(0 == token.compare("NCOLS_ODD:" )) {iss >> nColsOdd ; readColsOdd = true; + } else if(0 == token.compare("NCOLS_EVEN:" )) {iss >> nColsEven ; readColsEven = true; + } else if(0 == token.compare("NROWS:" )) {iss >> nRows ; readRows = true; + } else if(0 == token.compare("OPERATOR:" )) {iss >> operatorName ; readOperatorName = true; + } else if(0 == token.compare("SAMPLEID:" )) {iss >> sampleId ; readSampleId = true; + } else if(0 == token.compare("SCANID:" )) {iss >> scanId ; readScanId = true; + } else if(0 == token.compare("Phase" )) { + //check that all attributes for previous phase were read + std::stringstream ss; + ss << phaseList.size(); + if( !readPhaseMaterial ) throw std::runtime_error("ang missing material name for phase " + ss.str()); + if( !readPhaseFormula ) throw std::runtime_error("ang missing formula for phase " + ss.str()); + if( !readPhaseInfo ) throw std::runtime_error("ang missing info for phase " + ss.str()); + if( !readPhaseSymmetry ) throw std::runtime_error("ang missing symmetry for phase " + ss.str()); + if( !readPhaseLattice ) throw std::runtime_error("ang missing lattice constants for phase " + ss.str()); + if( !readPhaseHkl ) throw std::runtime_error("ang missing hkl families for phase " + ss.str()); + // if(6 != phaseElasticCount ) throw std::runtime_error("ang missing elastic constants for phase " + ss.str());//elastic constants are optional + // if( !readPhaseCategories) throw std::runtime_error("ang missing categories for phase " + ss.str());//categories are optional + if( !phaseList.empty() ) { + if(targetFamilies < phaseList.back().hklFam.size()) + throw std::runtime_error("ang missing some hkl families for phase " + ss.str()); + } + targetFamilies = phaseElasticCount = 0; + readPhaseSymmetry = readPhaseMaterial = readPhaseFormula = readPhaseInfo = false; + readPhaseLattice = readPhaseHkl = readPhaseCategories = false; + + //add a new blank phase to the list + phaseList.resize(phaseList.size() + 1); + iss >> phaseList.back().num; + } else if(0 == token.compare("MaterialName" )) {iss >> phaseList.back().name; readPhaseMaterial = true; + } else if(0 == token.compare("Formula" )) {iss >> phaseList.back().form; readPhaseFormula = true; + } else if(0 == token.compare("Info" )) {iss >> phaseList.back().info; readPhaseInfo = true; + } else if(0 == token.compare("Symmetry" )) {iss >> phaseList.back().sym ; readPhaseSymmetry = true; + } else if(0 == token.compare("NumberFamilies")) { + iss >> targetFamilies;//read the number of families + phaseList.back().hklFam.reserve(targetFamilies);//allocate space for the families + readPhaseHkl = true; + } else if(0 == token.compare("LatticeConstants")) { + iss >> phaseList.back().lat[0] + >> phaseList.back().lat[1] + >> phaseList.back().lat[2] + >> phaseList.back().lat[3] + >> phaseList.back().lat[4] + >> phaseList.back().lat[5]; + readPhaseLattice = true; + } else if(0 == token.compare("hklFamilies")) { + phaseList.back().hklFam.resize(phaseList.back().hklFam.size() + 1);//add new family (space was already reserved) + iss >> phaseList.back().hklFam.back().h + >> phaseList.back().hklFam.back().k + >> phaseList.back().hklFam.back().l + >> phaseList.back().hklFam.back().useIdx + >> phaseList.back().hklFam.back().intensity + >> phaseList.back().hklFam.back().showBands; + } else if(0 == token.compare("ElasticConstants")) { + iss >> phaseList.back().el[6*phaseElasticCount + 0] + >> phaseList.back().el[6*phaseElasticCount + 1] + >> phaseList.back().el[6*phaseElasticCount + 2] + >> phaseList.back().el[6*phaseElasticCount + 3] + >> phaseList.back().el[6*phaseElasticCount + 4] + >> phaseList.back().el[6*phaseElasticCount + 5]; + ++phaseElasticCount; + } else if(0 == token.compare(0, 10, "Categories")) {//tsl doesn't print space between categories and first number + //rewind to first character after categories key + iss.seekg((size_t) iss.tellg() + 10 - token.length()); + std::copy(std::istream_iterator(iss), std::istream_iterator(), std::back_inserter(phaseList.back().cats)); + readPhaseCategories = true; + } else { + if("VERSION" == token || + "HEADER:" == token || + "COLUMN_COUNT:" == token || + "COLUMN_HEADERS:" == token || + "COLUMN_UNITS:" == token) continue; + if("NOTES:" == token || "COLUMN_NOTES:" == token) { + std::string end = "# " + token + " End"; + iss >> token; + if("Start" == token) {//multi line notes + while(true) { + if('#' != is.peek()) throw std::runtime_error("header ended before end of multiline note"); + std::getline(is, line);//extract entire line from file + if(0 == line.compare(0, end.size(), end)) break;//end of notes + } + } + continue; + } + + throw std::runtime_error("unknown ang header keyword '" + token + "'"); + } + } + } + + //check that all values of final phase were read + std::stringstream ss; + ss << phaseList.size(); + if( !readPhaseMaterial ) throw std::runtime_error("ang missing material name for phase " + ss.str()); + if( !readPhaseFormula ) throw std::runtime_error("ang missing formula for phase " + ss.str()); + if( !readPhaseInfo ) throw std::runtime_error("ang missing info for phase " + ss.str()); + if( !readPhaseSymmetry ) throw std::runtime_error("ang missing symmetry for phase " + ss.str()); + if( !readPhaseLattice ) throw std::runtime_error("ang missing lattice constants for phase " + ss.str()); + if( !readPhaseHkl ) throw std::runtime_error("ang missing hkl families for phase " + ss.str()); + // if(6 != phaseElasticCount ) throw std::runtime_error("ang missing elastic constants for phase " + ss.str());//elastic constants are optional + // if( !readPhaseCategories) throw std::runtime_error("ang missing categories for phase " + ss.str());//categories are optional + if( !phaseList.empty() ) { + if(targetFamilies < phaseList.back().hklFam.size()) + throw std::runtime_error("ang missing some hkl families for phase " + ss.str()); + } + + //make sure all header values were read + if(!readPixPerUm ) throw std::runtime_error("missing ang header value TEM_PIXperUM" ); + if(!readXStar ) throw std::runtime_error("missing ang header value x-star" ); + if(!readYStar ) throw std::runtime_error("missing ang header value y-star" ); + if(!readZStar ) throw std::runtime_error("missing ang header value z-star" ); + if(!readWorkingDistance) throw std::runtime_error("missing ang header value WorkingDistance"); + if(!readGridType ) throw std::runtime_error("missing ang header value GRID" ); + if(!readXStep ) throw std::runtime_error("missing ang header value XSTEP" ); + if(!readYStep ) throw std::runtime_error("missing ang header value YSTEP" ); + if(!readColsOdd ) throw std::runtime_error("missing ang header value NCOLS_ODD" ); + if(!readColsEven ) throw std::runtime_error("missing ang header value NCOLS_EVEN" ); + if(!readRows ) throw std::runtime_error("missing ang header value NROWS" ); + if(!readOperatorName ) throw std::runtime_error("missing ang header value OPERATOR" ); + if(!readSampleId ) throw std::runtime_error("missing ang header value SAMPLEID" ); + if(!readScanId ) throw std::runtime_error("missing ang header value SCANID" ); + + //extract first line of data without advancing stream + const std::streamoff start = is.tellg();//save position of data start + std::getline(is, line);//copy first data line + is.seekg(start);//rewind stream to data start + + //get number of tokens + size_t tokenCount = 0; + std::istringstream iss(line); + while(iss >> token) tokenCount++;//count number of values in first data line + if(tokenCount < 8) {//make sure there are enough columns + std::stringstream ss; + ss << "unexpected number of ang values per point (got " << tokenCount << ", expected at least 8)"; + throw std::runtime_error(ss.str()); + } + return tokenCount; + } + + //@brief : read ang data using an input stream + //@param is : input stream set data start + //@param tokens: number of tokens per point + //@return : number of points (rows) parsed + size_t OrientationMap::readAngData(std::istream& is, size_t tokens) { + char line[512];//most ang files have 128 byte lines including '\n' so this should be plenty + struct PixData{ + float eu[3] ; + float x , y ; + float iq , ci ; + float sem, fit; + int8_t phase ; + + //@brief : lexicographic sort + //@param rhs: other pixel data to compare to + bool operator<(const PixData& rhs) const {return y < rhs.y ? true : x < rhs.x;} + }; + + //read all point (can be in arbitrary order) + size_t pointsRead = 0; + std::vector pixelData; + const size_t totalPoints = iq.size(); + pixelData.resize(totalPoints); + const bool readSem = tokens > 8; + const bool readFit = tokens > 9; + for(size_t i = 0; i < totalPoints; i++) { + char* data = line;//get pointer to line start + if(!is.getline(line, sizeof(line))) {//try to get line + pixelData.resize(i+1);//clip empty points + break;//get next line + } + PixData& p = pixelData[i]; + + //parse line + p.eu[0] = std::strtof(data, &data );//parse first euler angle + p.eu[1] = std::strtof(data, &data );//parse second euler angle + p.eu[2] = std::strtof(data, &data );//parse third euler angle + p.x = std::strtof(data, &data );//parse x + p.y = std::strtof(data, &data );//parse y + p.iq = std::strtof(data, &data );//parse image quality + p.ci = std::strtof(data, &data );//parse confidence index + p.phase = (int8_t)std::strtol(data, &data, 10);//parse phase (as base 10) + if(readSem) {//are there 9 or more tokens? + p.sem = std::strtof(data, &data);//parse SE signal + if(readFit) {//are there 10 or more tokens? + p.fit = std::strtof(data, NULL);//parse fit + } + } + } + + //now sort points and extract data + std::sort(pixelData.begin(), pixelData.end()); + for(size_t i = 0; i < pixelData.size(); i++) { + PixData& p = pixelData[i]; + eu [3*i ] = p.eu[0]; + eu [3*i+1] = p.eu[1]; + eu [3*i+2] = p.eu[2]; + x [i] = p.x ; + y [i] = p.y ; + iq [i] = p.iq ; + ci [i] = p.ci ; + phase[i] = p.phase; + if(readSem) {//are there 9 or more tokens? + sem[i] = p.sem; + if(readFit) {//are there 10 or more tokens? + fit[i] = p.fit; + } + } + } + return pixelData.size(); + } + +#ifdef XTAL_USE_H5 + //@brief : read data from a '.h5' file + //@param grp: folder to read data from + void OrientationMap::readH5(H5::Group grp) { + //first get header and data folders + H5::Group header = grp.openGroup("EBSD/Header"); + H5::Group data = grp.openGroup("EBSD/Data" ); + + //define some useful h5 parameters once + H5::DataSpace ds(H5S_SCALAR);//single element data space + H5::CompType hklType( sizeof(HKLFamily) );//compound type for hkl family + hklType.insertMember("H" , HOFFSET(HKLFamily, h ), H5::PredType::NATIVE_INT32); + hklType.insertMember("K" , HOFFSET(HKLFamily, k ), H5::PredType::NATIVE_INT32); + hklType.insertMember("L" , HOFFSET(HKLFamily, l ), H5::PredType::NATIVE_INT32); + hklType.insertMember("Diffraction Intensity", HOFFSET(HKLFamily, intensity), H5::PredType::NATIVE_FLOAT); + hklType.insertMember("Use in Indexing" , HOFFSET(HKLFamily, useIdx ), H5::PredType::NATIVE_INT8 ); + hklType.insertMember("Show bands" , HOFFSET(HKLFamily, showBands), H5::PredType::NATIVE_INT8 ); + + //now parse out header contents + std::string grid; + H5::DataSet dOp = header.openDataSet("Operator" ); + H5::DataSet dSa = header.openDataSet("Sample ID" ); + H5::DataSet dSc = header.openDataSet("Scan ID" ); + H5::DataSet dGr = header.openDataSet("Grid Type" ); + header.openDataSet("Pattern Center Calibration/x-star").read(&xStar , H5::PredType::NATIVE_FLOAT, ds); + header.openDataSet("Pattern Center Calibration/y-star").read(&yStar , H5::PredType::NATIVE_FLOAT, ds); + header.openDataSet("Pattern Center Calibration/z-star").read(&zStar , H5::PredType::NATIVE_FLOAT, ds); + header.openDataSet("Sample Tilt" ).read(&sampTlt , H5::PredType::NATIVE_FLOAT, ds); + header.openDataSet("Camera Elevation Angle" ).read(&camTlt , H5::PredType::NATIVE_FLOAT, ds); + header.openDataSet("Working Distance" ).read(&workingDistance, H5::PredType::NATIVE_FLOAT, ds); + header.openDataSet("Step X" ).read(&xStep , H5::PredType::NATIVE_FLOAT, ds); + header.openDataSet("Step Y" ).read(&yStep , H5::PredType::NATIVE_FLOAT, ds); + header.openDataSet("nColumns" ).read(&nColsOdd , H5::PredType::NATIVE_INT32, ds); nColsEven = nColsOdd; + header.openDataSet("nRows" ).read(&nRows , H5::PredType::NATIVE_INT32, ds); + dOp. read( operatorName , dOp.getStrType() , ds); + dSa. read( sampleId , dSa.getStrType() , ds); + dSc. read( scanId , dSc.getStrType() , ds); + dGr. read( grid , dGr.getStrType() , ds); std::stringstream(grid) >> gridType; + + //loop over phases + H5::Group pGrp = header.openGroup("Phase"); + const size_t numPhase = pGrp.getNumObjs(); + if(0 == numPhase) throw std::runtime_error("no phases in tsl HDF file"); + phaseList.resize(numPhase); + for(size_t i = 0; i < numPhase; i++) { + Phase& p = phaseList[i]; + std::stringstream ss; + ss << i + 1; + H5::Group phs = pGrp.openGroup(ss.str()); + p.num = i+1; + int32_t famNum = 0; + + H5::DataSet dMn = phs.openDataSet("MaterialName"); + H5::DataSet dFm = phs.openDataSet("Formula" ); + H5::DataSet dIn = phs.openDataSet("Info" ); + dMn .read( p.name , dMn.getStrType() , ds); + dFm .read( p.form , dFm.getStrType() , ds); + dIn .read( p.info , dIn.getStrType() , ds); + phs.openDataSet("Symmetry" ).read(&p.sym , H5::PredType::NATIVE_INT32, ds); + phs.openDataSet("Lattice Constant a" ).read(&p.lat[0], H5::PredType::NATIVE_FLOAT, ds); + phs.openDataSet("Lattice Constant b" ).read(&p.lat[1], H5::PredType::NATIVE_FLOAT, ds); + phs.openDataSet("Lattice Constant c" ).read(&p.lat[2], H5::PredType::NATIVE_FLOAT, ds); + phs.openDataSet("Lattice Constant alpha").read(&p.lat[3], H5::PredType::NATIVE_FLOAT, ds); + phs.openDataSet("Lattice Constant beta" ).read(&p.lat[4], H5::PredType::NATIVE_FLOAT, ds); + phs.openDataSet("Lattice Constant gamma").read(&p.lat[5], H5::PredType::NATIVE_FLOAT, ds); + phs.openDataSet("NumberFamilies" ).read(&famNum , H5::PredType::NATIVE_INT32, ds); + p.hklFam.resize(famNum); + phs.openDataSet("hkl Families" ).read(p.hklFam.data(), hklType); + } + + //open scan data + allocate(10);//allocate all arrays + const size_t scanPts = iq.size(); + H5::DataSet phi1 = data.openDataSet("Phi1" ); + H5::DataSet phi = data.openDataSet("Phi" ); + H5::DataSet phi2 = data.openDataSet("Phi2" ); + H5::DataSet dX = data.openDataSet("X Position"); + H5::DataSet dY = data.openDataSet("Y Position"); + H5::DataSet dIq = data.openDataSet("IQ" ); + H5::DataSet dCi = data.openDataSet("CI" ); + H5::DataSet dSem = data.openDataSet("SEM Signal"); + H5::DataSet dFit = data.openDataSet("Fit" ); + H5::DataSet dPhs = data.openDataSet("Phase" ); + + //sanity check sizes + hsize_t dSetPts[10] = {0}; + phi1.getSpace().getSimpleExtentDims(&dSetPts[0]); + phi .getSpace().getSimpleExtentDims(&dSetPts[1]); + phi2.getSpace().getSimpleExtentDims(&dSetPts[2]); + dX .getSpace().getSimpleExtentDims(&dSetPts[3]); + dY .getSpace().getSimpleExtentDims(&dSetPts[4]); + dIq .getSpace().getSimpleExtentDims(&dSetPts[5]); + dCi .getSpace().getSimpleExtentDims(&dSetPts[6]); + dSem.getSpace().getSimpleExtentDims(&dSetPts[7]); + dFit.getSpace().getSimpleExtentDims(&dSetPts[8]); + dPhs.getSpace().getSimpleExtentDims(&dSetPts[9]); + for(size_t i = 0; i < 10; i++) if(dSetPts[i] != scanPts) throw std::runtime_error("scan size / H5 dataset mismatch reading tsl H5 file"); + + //read scan data + std::vector work(scanPts); + phi1.read(work .data(), H5::PredType::NATIVE_FLOAT); + for(size_t i = 0; i < scanPts; i++) eu[3*i ] = work[i]; + phi .read(work .data(), H5::PredType::NATIVE_FLOAT); + for(size_t i = 0; i < scanPts; i++) eu[3*i+1] = work[i]; + phi2.read(work .data(), H5::PredType::NATIVE_FLOAT); + for(size_t i = 0; i < scanPts; i++) eu[3*i+2] = work[i]; + dX .read(x .data(), H5::PredType::NATIVE_FLOAT); + dY .read(y .data(), H5::PredType::NATIVE_FLOAT); + dIq .read(iq .data(), H5::PredType::NATIVE_FLOAT); + dCi .read(ci .data(), H5::PredType::NATIVE_FLOAT); + dSem.read(sem .data(), H5::PredType::NATIVE_FLOAT); + dFit.read(fit .data(), H5::PredType::NATIVE_FLOAT); + dPhs.read(phase.data(), H5::PredType::NATIVE_INT8 ); + } +#endif + + //@brief : write data to an '.ang' file + //@param fileName: name of ang file to write + void OrientationMap::writeAng(std::string fileName) { + std::ofstream os(fileName.c_str());//open file + writeAngHeader(os);//write data + writeAngData(os);//write data + } + + //@brief : write ang header + //@param os: output stream to write the header to + void OrientationMap::writeAngHeader(std::ostream& os) { + os << std::fixed << std::setprecision(6); + os << "# TEM_PIXperUM " << pixPerUm << '\n'; + os << "# x-star " << xStar << '\n'; + os << "# y-star " << yStar << '\n'; + os << "# z-star " << zStar << '\n'; + os << "# WorkingDistance " << workingDistance << '\n'; + os << "#\n"; + for(const Phase& phase : phaseList) { + os << "# Phase " << phase.num << '\n'; + os << "# MaterialName " << phase.name << '\n'; + os << "# Formula " << phase.form << '\n'; + os << "# Info " << phase.info << '\n'; + os << "# Symmetry " << phase.sym << '\n'; + os << "# NumberFamilies " << phase.hklFam.size() << '\n'; + os << std::setprecision(3); + os << "# LatticeConstants " << phase.lat[0] << ' ' + << phase.lat[1] << ' ' + << phase.lat[2] << ' ' + << phase.lat[3] << ' ' + << phase.lat[4] << ' ' + << phase.lat[5] << '\n'; + os << std::setprecision(6); + for(const HKLFamily& hkl : phase.hklFam) { + os << "# hklFamilies " << std::setw(3) << hkl.h << ' ' + << std::setw(3) << hkl.k << ' ' + << std::setw(3) << hkl.l << ' ' + << (int) hkl.useIdx << ' ' + << std::setw(9) << hkl.intensity << ' ' + << (int) hkl.showBands << '\n'; + } + for(size_t i = 0; i < 6; i++) { + os << "# ElasticConstants " << phase.el[6*i+0] << ' ' + << phase.el[6*i+1] << ' ' + << phase.el[6*i+2] << ' ' + << phase.el[6*i+3] << ' ' + << phase.el[6*i+4] << ' ' + << phase.el[6*i+5] << '\n'; + } + os << "# Categories"; + for(const size_t & c : phase.cats) os << c << ' '; + os << '\n'; + os << "#\n"; + } + os << "# GRID: " << gridType << '\n'; + os << "# XSTEP: " << xStep << '\n'; + os << "# YSTEP: " << yStep << '\n'; + os << "# NCOLS_ODD: " << nColsOdd << '\n'; + os << "# NCOLS_EVEN: " << nColsEven << '\n'; + os << "# NROWS: " << nRows << '\n'; + os << "#\n"; + os << "# OPERATOR: " << operatorName << '\n'; + os << "#\n"; + os << "# SAMPLEID: " << sampleId << '\n'; + os << "#\n"; + os << "# SCANID: " << scanId << '\n'; + os << "#\n"; + } + + //@brief : write ang data + //@param os : output stream to write data to + void OrientationMap::writeAngData(std::ostream& os) { + const bool writeSem = !sem.empty(); + const bool writeFit = !fit.empty() && writeSem; + const size_t pts = eu.size() / 3; + if(x .size() != pts) throw std::runtime_error("incomplete x data for ang"); + if(y .size() != pts) throw std::runtime_error("incomplete y data for ang"); + if(iq .size() != pts) throw std::runtime_error("incomplete iq data for ang"); + if(ci .size() != pts) throw std::runtime_error("incomplete ci data for ang"); + if(phase.size() != pts) throw std::runtime_error("incomplete phase data for ang"); + if(writeSem) { + if(sem.size() != pts) throw std::runtime_error("incomplete sem data for ang"); + if(writeFit) { + if(fit.size() != pts) throw std::runtime_error("incomplete fit data for ang"); + } + } + + for(size_t j = 0; j < nRows; j++) { + for(size_t i = 0; i < nColsOdd; i++) { + const size_t idx = j * nRows + i; + os << std::setprecision(5); + os << std::setw( 9) << eu [3*i ]; + os << ' ' << std::setw( 9) << eu [3*i+1]; + os << ' ' << std::setw( 9) << eu [3*i+2]; + os << ' ' << std::setw(12) << x [ i ]; + os << ' ' << std::setw(12) << y [ i ]; + os << std::setprecision(1); + os << ' ' << std::setw( 7) << iq [ i ]; + os << std::setprecision(3); + os << ' ' << std::setw( 6) << ci [ i ]; + os << ' ' << std::setw( 2) << (int)phase[ i ]; + if(writeSem) { + os << ' ' << std::setw( 6) << sem[i]; + if(writeFit) os << ' ' << std::setw( 6) << fit[i]; + } + os << '\n'; + } + } + + +/* + bool evenRow = true; + size_t pointsRead = 0; + size_t completeRowPoints = 0; + size_t currentCol = nColsEven - 1; + for(size_t idx = 0; idx < pts; idx++) { + const size_t i = completeRowPoints + currentCol;//get index of point currently being parsed + os << std::setprecision(5); + os << std::setw( 9) << eu [3*i ]; + os << ' ' << std::setw( 9) << eu [3*i+1]; + os << ' ' << std::setw( 9) << eu [3*i+2]; + os << ' ' << std::setw(12) << x [ i ]; + os << ' ' << std::setw(12) << y [ i ]; + os << std::setprecision(1); + os << ' ' << std::setw( 7) << iq [ i ]; + os << std::setprecision(3); + os << ' ' << std::setw( 6) << ci [ i ]; + os << ' ' << std::setw( 2) << (int)phase[ i ]; + if(writeSem) { + os << ' ' << std::setw( 6) << sem[i]; + if(writeFit) os << ' ' << std::setw( 6) << fit[i]; + } + os << '\n'; + if(0 == currentCol--) {//decrement current column and check if we've reached a new row + completeRowPoints += evenRow ? nColsEven : nColsOdd;//update increment to start of current row + evenRow = !evenRow;//are we currently on an even or odd row? + currentCol = evenRow ? nColsEven - 1 : nColsOdd - 1;//get number of point in new row + } + } + */ + } +} + +#endif//_tsl_h_ diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..1f7c908 --- /dev/null +++ b/license.txt @@ -0,0 +1,86 @@ +GNU GENERAL PUBLIC LICENSE + +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. +Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + +a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. +b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. +c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: + +a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, +b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, +c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) +The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS diff --git a/programs/CMakeLists.txt b/programs/CMakeLists.txt new file mode 100644 index 0000000..b3c89f7 --- /dev/null +++ b/programs/CMakeLists.txt @@ -0,0 +1,98 @@ +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # +# Copyright (c) 2019, De Graef Group, Carnegie Mellon University # +# All rights reserved. # +# # +# Author: William C. Lenthe # +# # +# This package is free software; you can redistribute it and/or # +# modify it under the terms of the GNU General Public License as # +# published by the Free Software Foundation; either version 2 of the # +# License, or (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program; if not, check the Free Software Foundation # +# website: # +# # +# # +# Interested in a commercial license? Contact: # +# # +# Center for Technology Transfer and Enterprise Creation # +# 4615 Forbes Avenue, Suite 302 # +# Pittsburgh, PA 15213 # +# # +# phone. : 412.268.7393 # +# email : innovation@cmu.edu # +# website: https://www.cmu.edu/cttec/ # +# # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +################################ +# add executables # +################################ + +add_executable(IndexEBSD ${CMAKE_CURRENT_LIST_DIR}/index_ebsd.cpp ) +add_executable(MasterXcorr ${CMAKE_CURRENT_LIST_DIR}/master_xcorr.cpp ) +add_executable(PatternRepack ${CMAKE_CURRENT_LIST_DIR}/pattern_repack.cpp) +add_executable(EBSPDims ${CMAKE_CURRENT_LIST_DIR}/ebsp_dims.cpp ) +add_executable(mp2sht ${CMAKE_CURRENT_LIST_DIR}/mp2sht.cpp ) +add_executable(sht2mp ${CMAKE_CURRENT_LIST_DIR}/sht2mp.cpp ) +add_executable(ShtWisdom ${CMAKE_CURRENT_LIST_DIR}/sht_wisdom.cpp ) + +if(${EMSPHINX_BUILD_GUIS}) + + set(MACOSX_BUNDLE_ICON_FILE sphinx.icns) # add icon to Info.plist + set(SPHINX_ICON ${CMAKE_SOURCE_DIR}/icons/sphinx.icns) # location of icon file in source + set_source_files_properties(${SPHINX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") # copy the icon file to the Resources folder of the bundle + + if(WIN32) + set(SPHINX_ICON ${CMAKE_SOURCE_DIR}/icons/sphinx.rc) # location of icon file in source + endif() + + add_executable(EMSphInxEBSD WIN32 MACOSX_BUNDLE ${CMAKE_CURRENT_LIST_DIR}/ebsd_wizard.cpp ${SPHINX_ICON}) +endif() + +################################ +# dependencies # +################################ + +if(${EMSPHINX_BUILD_FFTW}) + add_dependencies(IndexEBSD ${FFTW_DEPENDS}) + add_dependencies(MasterXcorr ${FFTW_DEPENDS}) + add_dependencies(mp2sht ${FFTW_DEPENDS}) + add_dependencies(sht2mp ${FFTW_DEPENDS}) + add_dependencies(ShtWisdom ${FFTW_DEPENDS}) +endif() +if(${EMSPHINX_BUILD_HDF5}) + add_dependencies(IndexEBSD hdf5) + add_dependencies(MasterXcorr hdf5) + add_dependencies(PatternRepack hdf5) + add_dependencies(EBSPDims hdf5) + add_dependencies(mp2sht hdf5) + add_dependencies(sht2mp hdf5) +endif() +if(${EMSPHINX_BUILD_GUIS}) + add_dependencies(EMSphInxEBSD hdf5 ${FFTW_DEPENDS}) +endif() + +################################ +# linking # +################################ + +# link +target_link_libraries(IndexEBSD ${FFTW_LIBRARIES} ${HDF5_LIBRARIES}) +target_link_libraries(MasterXcorr ${FFTW_LIBRARIES} ${HDF5_LIBRARIES}) +target_link_libraries(PatternRepack ${HDF5_LIBRARIES}) +target_link_libraries(EBSPDims ${HDF5_LIBRARIES}) +target_link_libraries(mp2sht ${FFTW_LIBRARIES} ${HDF5_LIBRARIES}) +target_link_libraries(sht2mp ${FFTW_LIBRARIES} ${HDF5_LIBRARIES}) +target_link_libraries(ShtWisdom ${FFTW_LIBRARIES} ) + +if(${EMSPHINX_BUILD_GUIS}) + target_link_libraries(EMSphInxEBSD ${FFTW_LIBRARIES} ${HDF5_LIBRARIES} core base propgrid) +endif() diff --git a/programs/ebsd_wizard.cpp b/programs/ebsd_wizard.cpp new file mode 100644 index 0000000..277a677 --- /dev/null +++ b/programs/ebsd_wizard.cpp @@ -0,0 +1,63 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +class MyApp: public wxApp { + bool OnInit(); +}; + +IMPLEMENT_APP(MyApp) + +#include "wx/EbsdNamelistWizard.h" + +#include "sphinx.xpm" + +bool MyApp::OnInit() { + EbsdNamelistWizard* wizard = new EbsdNamelistWizard(NULL); + +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) + static const bool setIcon = true; +#elif __APPLE__ || __linux__ || __unix__ || defined(_POSIX_VERSION) + #if __APPLE__ + static const bool setIcon = false;//already handled by bundle + #else + static const bool setIcon = true; + #endif +#endif + if(setIcon) wizard->SetIcon( wxICON(sphinx) ); + + wizard->Show(); + return true; +} diff --git a/programs/ebsp_dims.cpp b/programs/ebsp_dims.cpp new file mode 100644 index 0000000..124c426 --- /dev/null +++ b/programs/ebsp_dims.cpp @@ -0,0 +1,106 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include + +#include "modality/ebsd/pattern.hpp" +#include "util/timer.hpp" + +int main(int argc, char *argv[]) { + const bool binToFloat = false;//should binned values be converted up to a float or kept as their current type + + //sanity check argument count + if(2 != argc) { + std::cout << "usage: " << argv[0] << " inputFile\n"; + std::cout << "\tinputFile - pattern file to read (*.ebsp)\n"; + return EXIT_FAILURE; + } + + //get some pattern info + Timer t; + emsphinx::ebsd::OxfordPatternFile pats(argv[1]); + + std::cout << "found " << pats.numPat() << " patterns in " << argv[1] << ":\n"; + std::cout << "\twidth : " << pats.width () << '\n'; + std::cout << "\thegiht: " << pats.height() << '\n'; + std::cout << "\ttype : "; + switch(pats.pixelType()) { + case emsphinx::ImageSource::Bits::U8 : std::cout << "8 bit\n" ; break; + case emsphinx::ImageSource::Bits::U16: std::cout << "16 bit\n" ; break; + case emsphinx::ImageSource::Bits::F32: std::cout << "float\n" ; break; + case emsphinx::ImageSource::Bits::UNK: // intentional fall through + default: throw std::logic_error("unknown pixel type"); + } + std::cout << "\tbytes : " << pats.imBytes() << "\n"; + double mb = double(pats.imBytes() * pats.numPat()) / (1024*1024); + std::cout << "total size: "; + if(mb > 1024) { + std::cout << mb / 1024 << " GB\n"; + } else { + std::cout << mb << " MB\n"; + } + + //now loop over patterns accumulating all possible indices + const size_t batchSize = 100; + std::vector vx, vy; + std::set sx, sy; + + //finally loop over patterns writing into file + std::vector buff(pats.imBytes() * batchSize); + const size_t numBatch = (pats.numPat() + batchSize - 1) / batchSize; + for(size_t i = 0; i * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include + +#include "util/threadpool.hpp" +#include "util/timer.hpp" +#include "util/nml.hpp" +#include "util/sysnames.hpp" +#include "modality/ebsd/pattern.hpp" + +#include "modality/ebsd/nml.hpp" + +#include "modality/ebsd/idx.hpp" + +int main(int argc, char *argv[]) { + +try { + + typedef double Real;//should we use float, double, or long double for calculations? + + //////////////////////////////////////////////////////////////////////// + // Parse Arguments // + //////////////////////////////////////////////////////////////////////// + + //check argument count + if(2 != argc) { + std::cout << "useage: "; + std::cout << "\tindex using a nml file : " << argv[0] << " input.nml\n"; + std::cout << "\tgenerate a template nml: " << argv[0] << " -t\n"; + return EXIT_FAILURE; + } + + //check for template request + const std::string nmlName(argv[1]); + if(0 == nmlName.compare("-t")) { + std::ofstream os(std::string("IndexEBSD") + ".nml");//create programname.nml (in the future argv[0] could be used with std::filesystem to remove full path) + emsphinx::ebsd::Namelist nml; + nml.defaults(); + os << nml.to_string(); + return EXIT_SUCCESS; + } + + //read nml and parse + emsphinx::ebsd::Namelist nml; + { + std::ifstream is(nmlName); + std::string str((std::istreambuf_iterator(is)), std::istreambuf_iterator()); + std::string warning = nml.from_string(str); + if(!warning.empty()) { + std::cout << "\n * * * * * * warning: some namelist parameters weren't used: " << warning << " * * * * * * \n" << std::endl; + } + } + + //////////////////////////////////////////////////////////////////////// + // Read Inputs / Build Indexers // + //////////////////////////////////////////////////////////////////////// + + emsphinx::ebsd::IndexingData idxData(nml); + + //////////////////////////////////////////////////////////////////////// + // Print Info // + //////////////////////////////////////////////////////////////////////// + + //lets print some information before indexing gets started + std::cout << '\n' << std::boolalpha; + std::cout << "Running program \"" << argv[0] << "\"\n"; + std::cout << "\tCompiled From : " << __FILE__ << '\n'; + std::cout << "\tGit Branch : " << emsphinx::GitBranch << '\n'; + std::cout << "\tCommit Hash : " << emsphinx::GitHash << '\n'; + std::cout << "\tVersion String : " << emsphinx::Version << '\n'; + std::cout << '\n'; + std::cout << "Geometry\n"; + std::cout << "\tSample Tilt : " << idxData.phases.front().getSig() << " degrees\n"; + std::cout << "\tScintillator Distance: " << idxData.geom.sDst << " microns\n"; + std::cout << "\tCamera Tilt : " << idxData.geom.dTlt << " degrees\n"; + std::cout << "\tCamera : " << nml.patDims[0] << " x " << nml.patDims[1] << " with " << idxData.geom.pX << " micron pixels\n"; + std::cout << "\tPattern Center : " << idxData.geom.cX << ", " << idxData.geom.cY << " fractional pixels\n"; + std::cout << "\tCircular Mask : " << idxData.geom.circ << "\n"; + std::cout << "\tVertical Flip : " << idxData.geom.flip << "\n"; + std::cout << "\n"; + std::cout << "Indexing patterns from \"" << nml.patFile; + if(!nml.patName.empty()) std::cout << ":/" << nml.patName; + std::cout << "\"\n"; + std::cout << "\tScan Dimensions : " << nml.scanDims[0] << " x " << nml.scanDims[1] << " pixels\n"; + std::cout << "\tScan Resolution : " << nml.scanSteps[0] << " x " << nml.scanSteps[1] << " microns\n"; + std::cout << "\tPattern bitdepth : " << idxData.pat->pixBytes() * 8 << '\n'; + std::cout << "\tTotal Patterns : " << idxData.pat->numPat() << '\n'; + std::cout << "\tAHE grid points : " << nml.nRegions << '\n'; + std::cout << "\n"; + std::cout << "Against master pattern" << (idxData.phases.size() > 1 ? "s" : "") << ":\n"; + for(size_t i = 0; i < nml.masterFiles.size(); i++) { + std::cout << "\tFile Name : " << nml.masterFiles[i] << '\n'; + std::cout << "\tPoint Group : " << idxData.phases[i].pointGroup().name() << '\n'; + std::cout << "\tZ Rotational Symmetry: " << (int)idxData.phases[i].pointGroup().zRot() << '\n'; + std::cout << "\tEquatorial Mirror : " << (idxData.phases[i].pointGroup().zMirror() ? "yes" : "no") << '\n'; + } + std::cout << "\n"; + std::cout << "Indexing with\n"; + std::cout << "\tBandwidth : " << nml.bw << '\n'; + std::cout << "\tSide Length : " << fft::fastSize(uint32_t(2 * nml.bw - 1)) << '\n'; + std::cout << "\tROI Mask : " << ( !nml.roi.hasShape() ? "entire scan" : nml.roi.to_string() )<< '\n'; + std::cout << "\tThread Count : " << idxData.threadCount << '\n'; + std::cout << "\tBatch Size : " << nml.batchSize << '\n'; + std::cout.flush(); + + // const std::pair scaleSize = idxData.indexers.front()->rescaledSize(); + // std::cout << "\tPatterns Resized To : " << scaleSize.first << 'x' << scaleSize.second << '\n'; + std::cout << std::endl; + + //////////////////////////////////////////////////////////////////////// + // Do Indexing // + //////////////////////////////////////////////////////////////////////// + + //build thread pool and get start time + ThreadPool pool(idxData.threadCount);//pool + time_t tmStart = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + + //queue parallel indexing + Timer t; + size_t batches = idxData.pat->numPat() / nml.batchSize;//how many batches are needed + if(batches * nml.batchSize < idxData.pat->numPat()) ++batches;//extra batch for leftovers + for(size_t i = 0; i < batches; i++) {//loop over batches + const size_t start = i * nml.batchSize;//first pattern + const size_t end = std::min(start + nml.batchSize, idxData.pat->numPat());//last pattern + pool.schedule(std::bind(idxData.workItem, std::placeholders::_1));//queue indexing + } + + //wait for indexing to complete + const std::chrono::milliseconds uptFreq(1000);//milliseconds between updates + while(!pool.waitAll(uptFreq)) { + //get the time elapsed since we started (without resetting the reference point) + const double elapsed = t.poll(false); + const double percent = double(idxData.idxCtr) / idxData.numIdx; + const double rate = elapsed / percent;//estimate seconds per % + const double remaining = rate * (1.0 - percent);//estimate seconds left + + //print update + std::cout << '\r'; + Timer::PrintSeconds(elapsed , std::cout); + std::cout << " elapsed, " << std::fixed << std::setprecision(1) << percent * 100 << "% complete, "; + Timer::PrintSeconds(remaining, std::cout); + std::cout << " remaining "; + std::cout.flush(); + } + + const double total = t.poll(); + time_t tmEnd = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + std::cout << '\n' << total << "s to index (" << double(idxData.numIdx) / total << " pat/s)\n"; + + //////////////////////////////////////////////////////////////////////// + // Save Outputs // + //////////////////////////////////////////////////////////////////////// + + idxData.save(tmStart, tmEnd, total); + + //done + return 0; + +} catch (std::exception& e) { + std::cout << e.what() << '\n'; + return EXIT_FAILURE; +} + +} diff --git a/programs/master_xcorr.cpp b/programs/master_xcorr.cpp new file mode 100644 index 0000000..85bb0a7 --- /dev/null +++ b/programs/master_xcorr.cpp @@ -0,0 +1,379 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "util/timer.hpp" +#include "idx/master.hpp" +#include "sht/sht_xcorr.hpp" +#include "xtal/diagram.hpp" +#include "constants.hpp" + +int main(int argc, char *argv[]) { + //parse arguments (file names / bandwidth) + if(4 != argc && 5 != argc) { + std::cout << "usage: " << argv[0] << "scanFile bandWidth cutoff masterFile1 [masterFile2]\n"; + std::cout << "\tbandWidth : bandwidth for cross correlation (2*bw-1 should be product of small primes)\n"; + std::cout << "\t 88, 95, 113, 123, 158, 172, 188, 203, 221, 263, and 284 are reasonable values\n"; + std::cout << "\tcutoff : cutoff for peak consideration [0,1] (relative to maximum cross correlation)\n"; + std::cout << "\tmasterFile1: name of first master pattern file (e.g. Ni.h5)\n"; + std::cout << "\tmasterFile2: name of second master pattern file (e.g. Ni.h5)\n"; + std::cout << "\nnote: only symmetry of first pattern will be used for to improve calculation speed\n"; + return EXIT_FAILURE; + } + const uint16_t bw = (uint16_t)std::strtoul(argv[1], NULL, 0); + const double cutoff = atof(argv[2]); + if(cutoff < 0.0 || cutoff > 1.0) throw std::runtime_error("cutoff must lie in [0,1]"); + std::string masterFile1( argv[3] ); + std::string masterFile2(argc == 5 ? argv[4] : masterFile1); + + //define some values we may want to parse in the future + std::string outputFile("pseudo_sym.h5"); + const double factor = 0.95;//how far below the target cutoff should we search to allow for off grid peaks + + //sanity check bandwidth (anything is technically ok but it is probably a good idea to restrict the range) + if(bw < 53 ) throw std::runtime_error("bandwidth too small"); + if(bw > 313) throw std::runtime_error("bandwidth too large"); + + //read master pattern(s) and compute their SHT + Timer t; + emsphinx::MasterPattern mp1(masterFile1);//read master pattern + emsphinx::MasterPattern mp2(masterFile2);//read master pattern + std::cout << t.poll() << "s to read master patterns" << std::endl; + emsphinx::MasterSpectra p1(mp1, bw, true);//compute harmonics of normed master pattern + emsphinx::MasterSpectra p2(mp2, bw, true);//compute harmonics of normed master pattern + std::cout << t.poll() << "s to compute SHT of patterns" << std::endl; + p1.removeDC();//it should already be almost zero from nrm == true in construction + p2.removeDC();//it should already be almost zero from nrm == true in construction + + //compute master pattern cross correlation + double eu[3];//we need somewhere to write peak correlation (hopefully it is 0...) + emsphinx::sphere::Correlator s2Corr(bw); + std::cout << t.poll() << "s to build correlator" << std::endl; + s2Corr.correlate(p1.data(), p2.data(), p1.mirror(), p1.nFold(), eu, false);//correlate + std::cout << t.poll() << "s to correlate\n"; + + //find peak intensity (no rotation for auto-correlation) + const size_t sl = bw * 2 - 1;//side length of euler space grid + if(masterFile1 == masterFile2) {//auto correlation + const size_t idxIdent = (bw-1) * sl * sl + (bw / 2) * sl + bw / 2;//no rotation + s2Corr.indexEuler(idxIdent, eu); + } + const double vMax = s2Corr.refinePeak(p1.data(), p2.data(), p1.mirror(), p1.nFold(), eu); + std::cout << "maximum intensity: " << vMax << std::endl; + + //now find all local maxima + struct Maxima { + double intensity; + size_t index; + xtal::Quat qu; + inline bool operator<(const Maxima& m) const {return intensity > m.intensity;}//reverse sorting order + }; + Maxima mx; + double nh[3][3][3]; + std::vector maxima; + const double vMin = vMax * cutoff * factor;//don't even both checking neighborhoods around pixels that aren't bright enough + + //loop over cross correlation grid searching for local maxima + fft::vector xc(s2Corr.getXC()); + for(size_t k = 0; k < bw; k++) {//loop over slices + for(size_t n = 0; n < sl; n++) {//loop over rows + for(size_t m = 0; m < sl; m++) {//loop over columns + const size_t idx = k * sl * sl + n * sl + m;//compute vectorized index + if(xc[idx] >= vMin) {//check if this voxel is bright enough to consider + s2Corr.extractNeighborhood<1>(idx, nh);//get 3x3x3 neighborhood surrounding point + if(nh[1][1][1] >= nh[0][0][0] &&//check if this is a local maxima + nh[1][1][1] >= nh[0][0][1] && + nh[1][1][1] >= nh[0][0][2] && + nh[1][1][1] >= nh[0][1][0] && + nh[1][1][1] >= nh[0][1][1] && + nh[1][1][1] >= nh[0][1][2] && + nh[1][1][1] >= nh[0][2][0] && + nh[1][1][1] >= nh[0][2][1] && + nh[1][1][1] >= nh[0][2][2] && + nh[1][1][1] >= nh[1][0][0] && + nh[1][1][1] >= nh[1][0][1] && + nh[1][1][1] >= nh[1][0][2] && + nh[1][1][1] >= nh[1][1][0] && + nh[1][1][1] >= nh[1][1][2] && + nh[1][1][1] >= nh[1][2][0] && + nh[1][1][1] >= nh[1][2][1] && + nh[1][1][1] >= nh[1][2][2] && + nh[1][1][1] >= nh[2][0][0] && + nh[1][1][1] >= nh[2][0][1] && + nh[1][1][1] >= nh[2][0][2] && + nh[1][1][1] >= nh[2][1][0] && + nh[1][1][1] >= nh[2][1][1] && + nh[1][1][1] >= nh[2][1][2] && + nh[1][1][1] >= nh[2][2][0] && + nh[1][1][1] >= nh[2][2][1] && + nh[1][1][1] >= nh[2][2][2] + ) { + //build maxima + mx.index = idx; + mx.intensity = nh[1][1][1] / vMax; + s2Corr.indexEuler(idx, eu); + xtal::zyz2qu(eu, mx.qu.data()); + + //find the closest existing maxima + size_t iNear = 0;//index of nearest maxima + double iDist = 360.;//distance to nearest maxima + for(size_t i = 0; i < maxima.size(); i++) {//loop over maxima + const double dot = std::min(1, std::fabs( mx.qu.dot(maxima[i].qu) ) ); + const double angle = std::acos(dot) * xtal::Constants::rd2dg; + if(angle < iDist) {//is this the closest maxima so far? + iNear = i; + iDist = angle; + } + } + + //make sure this isn't the same as an orientation we've already found + if(iDist < 2.0) {//are we within 2 degrees (this is extremely arbitrary) + if(mx.intensity > maxima[iNear].intensity) { + maxima[iNear] = mx;//keep the better maxima + } + } else { + maxima.push_back(mx);//add a new maxima + } + } + } + } + } + } + + //now sort local maxima by intensity + std::sort(maxima.begin(), maxima.end()); + std::cout << t.poll() << "s to extract and sort " << maxima.size() << " local maxima\n"; + if(maxima.empty()) { + std::cout << "no local maxima found in autocorrelation\n"; + return EXIT_FAILURE; + } + + //work through maxima refining + for(size_t i = 0; i < maxima.size(); i++) { + s2Corr.indexEuler(maxima[i].index, eu); + maxima[i].intensity = s2Corr.refinePeak(p1.data(), p2.data(), p1.mirror(), p1.nFold(), eu) / vMax; + xtal::zyz2qu(eu, maxima[i].qu.data()); + } + std::cout << t.poll() << "s to refine\n"; + + //now resort and print + std::sort(maxima.begin(), maxima.end()); + std::cout << std::fixed << std::setprecision(4); + for(size_t i = 0; i < maxima.size(); i++) { + if(maxima[i].intensity < cutoff) break; + std::cout << maxima[i].intensity << ' ' << maxima[i].qu << '\n'; + } + + //normalize entire dataset by brightest pixel + // std::for_each(xc.begin(), xc.end(), [vMax](double& v){v /= vMax;}); + + //create file to write result to + hsize_t dims[3] = {bw, sl, sl}; + H5::H5File file = H5::H5File(outputFile, H5F_ACC_TRUNC);//overwrite existing if needed + file.createDataSet("Cross Correlation", H5::PredType::NATIVE_DOUBLE, H5::DataSpace(3, dims)).write(xc.data(), H5::PredType::NATIVE_DOUBLE); + + //now let's make and xdmf file for easy visualization + std::ofstream os("pseudo_sym.xdmf"); + const double res = 360.0 / sl;//angular resolution in degrees + os << "\n"; + os << "\n"; + os << "\n"; + os << " \n"; + os << " \n"; + os << " \n"; + os << " \n"; + os << " 0 0 0\n"; + os << " " << res << ' ' << res << ' ' << res << "\n"; + os << " \n"; + os << " \n"; + os << " \n"; + os << " " << outputFile << ":/Cross Correlation\n"; + os << " \n"; + os << " \n"; + os << " \n"; + os << " \n"; + os << "\n"; + + //next create diagram + svg::Color c(0, 0, 0); + xtal::Diagram diag(mp1, c); + diag.getHemi().write("true.svg");//save true symmetry + + //get rotational symmetry operators of the point group + const size_t numOp = mp1.pointGroup().numRotOps (); + xtal::Quat const * const ops = (xtal::Quat const *)mp1.pointGroup().rotOps(); + + //loop over detected psuedosymmetry + const double degCut = 0.999;//cosine(same rotation) ~2.5 degrees + const double rotCut = 0.05;//cutoff for something to be considered an n fold axis + for(size_t i = 0; i < maxima.size(); i++) { + if(maxima[i].intensity < cutoff) break; + + //make sure this isn't a symmetry operator + bool match = false; + for(size_t j = 0; j < numOp; j++) { + const double dot = std::fabs(ops[j].dot(maxima[i].qu)); + if(dot > degCut) {//~2.5 degrees + match = true; + break; + } + } + + //this isn't a symmetry operator + if(!match) { + //make sure we haven't already added this operator + for(size_t j = 0; j < i; j++) { + const double dot = std::fabs(maxima[i].qu.dot(maxima[j].qu)); + if(dot > degCut) {//~2.5 degrees + match = true; + break; + } + } + + if(!match) {//if we made it this far this is a new pseudo symmetry operator + c.rgb[0] = c.rgb[1] = maxima[i].intensity;//scale color of operator with intensity of pseudo symmetry + const double order = emsphinx::Constants::pi / (std::acos(maxima[i].qu.w));//get order of rotation axis + const double nFld = std::round(order);//what is the closest integer order + if(std::fabs(nFld - nFld) < rotCut) { + //this is a crystallographic pseudosymmetry operator, use the right symbol + diag.setColor(c.rgb[0], c.rgb[1], c.rgb[2]); + diag.addRotor(maxima[i].qu.x, maxima[i].qu.y, maxima[i].qu.z, (int)nFld, 0.67); + } else { + //just use a dot for other rotations + diag.addDot(maxima[i].qu.data()+1, c, 0.67); + } + } + } + } + + //save updated diagram + diag.getHemi().write("pseudo.svg"); + return 0; + //build a weighted histogram of misorientation angles + + //rescale xc to [0,1] + /* + auto minMax = std::minmax_element(s2Corr.xc.cbegin(), s2Corr.xc.cend()); + const double vMin = *(minMax.first); + const double range = *(minMax.second) - *(minMax.first); + for(double& v : s2Corr.xc) { + v = (v - vMin) / range; + } + + */ + + // //build volume elements + // // const double dv0 = xtal::Constants::pi2 * xtal::Constants::pi2 * xtal::Constants::pi2 / (sl * sl * sl); + // const double dv0 = xtal::Constants::pi / (sl * sl * sl);// normalized by 8 pi^2 + // std::vector dv(bw+1); + // for(size_t k = 0; k <= bw; k++) { + // double eu[3]; + // s2Corr.indexEuler(k * sl * sl, eu); + // dv[k] = dv0 * std::fabs(std::sin(eu[1]));//sin(beta) + // } + + //normalize cross correlation + /* + double mean = 0, den = 0; + for(size_t k = 0; k <= bw; k++) { + std::vector::const_iterator iter = s2Corr.xc.cbegin() + k * sl * sl; + mean += std::accumulate(iter, iter + sl * sl, 0.0) * dv[k]; + den += dv[k] * sl * sl; + } + mean /= den; + for(double& v : s2Corr.xc) v -= mean; + double stdev = 0; + for(size_t k = 0; k <= bw; k++) { + std::vector::const_iterator iter = s2Corr.xc.cbegin() + k * sl * sl; + stdev += std::inner_product(iter, iter + sl * sl, iter, 0.0) * dv[k]; + } + stdev = std::sqrt(stdev / den); + for(double& v : s2Corr.xc) v /= stdev; + */ + /* + const double mean = std::accumulate(s2Corr.xc.cbegin(), s2Corr.xc.cend(), 0.0) / s2Corr.xc.size(); + for(double& v : s2Corr.xc) v -= mean; + const double stdev = std::sqrt(std::inner_product(s2Corr.xc.cbegin(), s2Corr.xc.cend(), s2Corr.xc.cbegin(), 0.0) / s2Corr.xc.size()); + for(double& v : s2Corr.xc) v /= stdev; + + + std::ofstream ofs("xcorr.raw", std::ios::out | std::ios::binary); + ofs.write((char*)s2Corr.xc.data(), s2Corr.xc.size() * sizeof(double)); + + //construct misorientation angle bins + const size_t numBins = sl; + double maxDiso = 180; + std::vector bins(numBins);//bin limits + std::vector cnts(numBins, 0);//counts + std::vector wgts(numBins, 0);//weights + for(size_t i = 0; i < numBins; i++) bins[i] = maxDiso * (i+1) / numBins; + + //loop over euler grid + xtal::Quat qi = xtal::Quat::Identity(); + for(size_t k = 0; k <= bw; k++) { + for(size_t m = 0; m < sl; m++) { + for(size_t n = 0; n < sl; n++) { + //get euler angle at this grid point + const size_t idx = k * sl * sl + m * sl + n; + double eu[3]; + s2Corr.indexEuler(idx, eu); + + //convert to quaternion and get misorientation + xtal::Quat qu; + xtal::zyz2qu(eu, qu.data()); + p.pointGroup().disoQu(qu.data(), qi.data(), qu.data()); + + //find misorientation bin + const double w = std::acos(qu.w) * 2 * xtal::Constants::rd2dg;//misorientatino angle + const size_t i = std::distance(bins.cbegin(), std::upper_bound(bins.cbegin(), bins.cend(), w)); + + //compute volume element and accumulate + if(i < bins.size()) { + wgts[i] += dv[k]; + cnts[i] += dv[k] * std::exp(s2Corr.xc[idx]); + // cnts[i] += dv[k] * s2Corr.xc[idx]; + } + } + } + } + const double sumWgt = std::accumulate(cnts.cbegin(), cnts.cend(), 0.0); + + //write histogram to file + std::ofstream csv("hist.csv"); + csv << "w,cnt,wgt\n"; + for(size_t i = 0; i < bins.size(); i++) { + csv << bins[i] << ',' << cnts[i]/sumWgt << ',' << wgts[i] << '\n'; + } + */ + return 0; +} diff --git a/programs/mp2sht.cpp b/programs/mp2sht.cpp new file mode 100644 index 0000000..c937925 --- /dev/null +++ b/programs/mp2sht.cpp @@ -0,0 +1,221 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include "idx/master.hpp" +#include "sht_file.hpp" +/* + +#define MINIZ_NO_STDIO +#define MINIZ_NO_TIME +#define MINIZ_NO_ZLIB_APIS +#include "miniz/miniz.c" +//@brief : extract the crystal data from an EMsoft master pattern file +//@param fn: name of file to extract from +sht::CrystalData extractXtal(char * fn) { + //read scalar data + int v; + sht::CrystalData xtal; + H5::H5File file = H5::H5File(fn, H5F_ACC_RDONLY);//read only access + file.openGroup("CrystalData").openDataSet("SpaceGroupNumber" ).read(&v , H5::PredType::NATIVE_INT ); xtal.sgNum () = (uint8_t) v; + file.openGroup("CrystalData").openDataSet("SpaceGroupSetting").read(&v , H5::PredType::NATIVE_INT ); xtal.sgSet () = (uint8_t) v; + file.openGroup("CrystalData").openDataSet("LatticeParameters").read(xtal.lat(), H5::PredType::NATIVE_FLOAT); + file.openGroup("CrystalData").openDataSet("Natomtypes" ).read(&v , H5::PredType::NATIVE_INT ); xtal.numAtoms() = (uint16_t) v; + + //read atom data + std::vector atomTypes(v); + std::vector atomData(v * 5); + file.openGroup("CrystalData").openDataSet("AtomData" ).read(atomData .data(), H5::PredType::NATIVE_FLOAT); + file.openGroup("CrystalData").openDataSet("Atomtypes").read(atomTypes.data(), H5::PredType::NATIVE_INT ); + + + xtal.sgAxis() = sht::CrystalData::Axis::Default; + xtal.sgCell() = sht::CrystalData::Cell::Default; + xtal.oriX() = xtal.oriY() = xtal.oriZ() = 0.0f; + xtal.rot()[0] = 1.0f; + xtal.rot()[1] = xtal.rot()[2] = xtal.rot()[3] = 0.0f; + xtal.weight() = 1.0f; + xtal.atoms.resize(v); + + for(int i = 0; i < v; i++) { + xtal.atoms[i].x () = atomData [i*5 + 0]; + xtal.atoms[i].y () = atomData [i*5 + 1]; + xtal.atoms[i].z () = atomData [i*5 + 2]; + xtal.atoms[i].occ () = atomData [i*5 + 3]; + xtal.atoms[i].charge() = 0.0f; + xtal.atoms[i].debWal() = atomData [i*5 + 4]; + xtal.atoms[i].resFp () = 0.0f; + xtal.atoms[i].atZ () = atomTypes[i ]; + } + return xtal; +} +*/ + +int main(int argc, char *argv[]) { + + //sanity check argument count + //this should be adjusted in the future to allow batch conversion since there is a good amount of overhead in building the transformer + if(3 != argc) { + std::cout << "usage: " << argv[0] << " inputFile outputFile\n"; + std::cout << "\tinputFile - master pattern to read (*.h5)\n"; + std::cout << "\toutputFile - spherical hamrnoics file to write (*.spx)\n"; + return EXIT_FAILURE; + } + + try { + //read in the master pattern and convert to spectrum + const size_t bw = 384; + const bool nrm = true; + emsphinx::MasterSpectra spec(emsphinx::MasterPattern(argv[1]), bw, nrm); + + //read in crystal data + float lat[6]; + int32_t sgN, sgS, nAt; + H5::H5File file = H5::H5File(argv[1], H5F_ACC_RDONLY);//read only access + file.openGroup("CrystalData").openDataSet("SpaceGroupNumber" ).read(&sgN, H5::PredType::NATIVE_INT32); + file.openGroup("CrystalData").openDataSet("SpaceGroupSetting").read(&sgS, H5::PredType::NATIVE_INT32); + file.openGroup("CrystalData").openDataSet("LatticeParameters").read( lat, H5::PredType::NATIVE_FLOAT); + file.openGroup("CrystalData").openDataSet("Natomtypes" ).read(&nAt, H5::PredType::NATIVE_INT32); + std::vector aTy(nAt); + std::vector aCd(nAt * 5); + file.openGroup("CrystalData").openDataSet("AtomData" ).read(aCd .data(), H5::PredType::NATIVE_FLOAT); + file.openGroup("CrystalData").openDataSet("Atomtypes").read(aTy.data(), H5::PredType::NATIVE_INT32); + + //read in simulation data + float fprm[15]; + int32_t iprm[5]; + + file.openGroup("NMLparameters").openGroup("MCCLNameList" ).openDataSet("sig" ).read(fprm + 0, H5::PredType::NATIVE_FLOAT); + fprm[1] = NAN;//sig end + fprm[2] = NAN;//sig step + file.openGroup("NMLparameters").openGroup("MCCLNameList" ).openDataSet("omega" ).read(fprm + 3, H5::PredType::NATIVE_FLOAT); + file.openGroup("NMLparameters").openGroup("MCCLNameList" ).openDataSet("EkeV" ).read(fprm + 4, H5::PredType::NATIVE_FLOAT); + file.openGroup("NMLparameters").openGroup("MCCLNameList" ).openDataSet("Ehistmin" ).read(fprm + 5, H5::PredType::NATIVE_FLOAT); + file.openGroup("NMLparameters").openGroup("MCCLNameList" ).openDataSet("Ebinsize" ).read(fprm + 6, H5::PredType::NATIVE_FLOAT); + file.openGroup("NMLparameters").openGroup("MCCLNameList" ).openDataSet("depthmax" ).read(fprm + 7, H5::PredType::NATIVE_FLOAT); + file.openGroup("NMLparameters").openGroup("MCCLNameList" ).openDataSet("depthstep" ).read(fprm + 8, H5::PredType::NATIVE_FLOAT); + fprm[9] = std::numeric_limits::infinity();//thickness + file.openGroup("NMLparameters").openGroup("BetheList" ).openDataSet("c1" ).read(fprm + 10, H5::PredType::NATIVE_FLOAT); + file.openGroup("NMLparameters").openGroup("BetheList" ).openDataSet("c2" ).read(fprm + 11, H5::PredType::NATIVE_FLOAT); + file.openGroup("NMLparameters").openGroup("BetheList" ).openDataSet("c3" ).read(fprm + 12, H5::PredType::NATIVE_FLOAT); + file.openGroup("NMLparameters").openGroup("BetheList" ).openDataSet("sgdbdiff" ).read(fprm + 13, H5::PredType::NATIVE_FLOAT); + file.openGroup("NMLparameters").openGroup("EBSDMasterNameList").openDataSet("dmin" ).read(fprm + 14, H5::PredType::NATIVE_FLOAT); + + file.openGroup("NMLparameters").openGroup("MCCLNameList" ).openDataSet("totnum_el" ).read(iprm + 0, H5::PredType::NATIVE_INT32); + file.openGroup("NMLparameters").openGroup("MCCLNameList" ).openDataSet("multiplier").read(iprm + 1, H5::PredType::NATIVE_INT32); + file.openGroup("NMLparameters").openGroup("MCCLNameList" ).openDataSet("numsx" ).read(iprm + 2, H5::PredType::NATIVE_INT32); + file.openGroup("NMLparameters").openGroup("EBSDMasterNameList").openDataSet("npx" ).read(iprm + 3, H5::PredType::NATIVE_INT32); + iprm[4] = 1;//lattitude grid type + + //build compression flags + // int8_t flg[2]; + // flg[0] = spec.nFold(); + // flg[1] = (spec.invSym() ? 1 : 0) + (spec.mirror() ? 2 : 0) + (4 * spec.phase().pg.mmType()); + + //@brief : write a file using EMsoft style EBSD data + //@prief fn : file name to write + //@prief nt : notes string + //@param sgN: space group number [1,230] + //@param sgS: space group setting [1,2] + //@param nAt: number of atoms + //@param aTy: atom types (nAt atomic numbers) + //@param aCd: atom coordinates, (nAt * 5 floats {x, y, z, occupancy, Debye-Waller in nm^2}) + //@param lat: lattice parameters {a, b, a, alpha, beta, gamma} (in nm / degree) + //@param fprm: floating point parameters (float32 EMsoftED parameters in order) + //@param iprm: integer parameters {# electrons, electron multiplier, numsx, npx, latgridtype} + //@param bw : bandwidth + //@param flg: symmetry flags {zRot, mirInv} + //@param alm: actual harmonics (uncompressed format) + std::string notes("notes string"); + std::string doi("doi string"); + sht::File::EMsoftEBSD(argv[2], notes.c_str(), doi.c_str(), sgN, sgS, nAt, aTy.data(), aCd.data(), lat, fprm, iprm, (int32_t)bw, (double*)spec.data()); + + + + +/* + + + + sht::File file; + + //build header info + file.header.modality() = sht::Modality::EBSD; + file.header.setDoi("doistr"); + file.header.setNotes("file notes"); + + //vendor + simullen set by file + + file.header.beamEnergy () = (float) spec.getKv (); + file.header.primaryAngle() = (float) spec.getSig(); + + //build material data + file.material.xtals.push_back(extractXtal(argv[1])); + file.material.numXtal() = 1; + file.material.sgEff () = file.material.xtals.front().sgNum(); + + //build simulation meta data + // std::unique_ptr simulMeta;//can be null if header.simDataSize() == 0 + + //extract harmonics + const int32_t nHarm = sht::HarmonicsData::NumHarm(bw, (int8_t)spec.nFold(), spec.invSym(), spec.mirror()); + file.harmonics.bw () = bw; + file.harmonics.zRot () = (int8_t) spec.nFold(); + file.harmonics.mirInv () = (spec.invSym() ? 1 : 0) | (spec.mirror() ? 2 : 0); + file.harmonics.doubCnt() = nHarm * 2; + file.harmonics.alm.resize(nHarm * 2); + double * pHrm = file.harmonics.alm.data(); + for(size_t m = 0; m < bw; m++) { + std::complex * const row = spec.data() + bw * m; + if(spec.nFold() < 2 ? false : 0 != m % spec.nFold()) continue;//systemic zeros + for(size_t l = m; l < bw; l++) { + if( (spec.invSym() && 0 != l % 2) || (spec.mirror() && 0 != (l + m) % 2) ) continue; + *pHrm++ = row[l].real(); + *pHrm++ = row[l].imag(); + } + } + + //if we made it this far everything needed was parsed, write out the result + std::ofstream os(argv[2], std::ios::out | std::ios::binary); + file.write(os); + */ + return EXIT_SUCCESS; + } catch (std::exception& e) { + std::cout << e.what() << '\n'; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; + +} diff --git a/programs/pattern_repack.cpp b/programs/pattern_repack.cpp new file mode 100644 index 0000000..625e42c --- /dev/null +++ b/programs/pattern_repack.cpp @@ -0,0 +1,296 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include +#include + +#include "modality/ebsd/pattern.hpp" +#include "util/timer.hpp" +#include "H5Cpp.h" + +//@brief : in place bin a pattern (binned pattern will be converted to floats and summed) +//@param in : raw buffer holding pattern (of pixel type T) +//@param out: location to write output (cannot overlap with input) +//@param bin: binning factor +//@param w : binned pattern width (input width is w * bin) +//@param h : binned pattern height (input height is h * bin) +template +void binFloat(T const* in, float* out, const size_t bin, const size_t w, const size_t h) { + if(0 == bin){ + throw std::runtime_error("bin factor must be positive"); + } else if(1 == bin) { + std::transform(in, in + w * h, out, [](const T& i){return (float)i;});//handle trivial case + } else { + for(size_t j = 0; j < h; j++) {//loop over output rows + std::fill(out, out + w, 0.0f);//initialize output with zero + for(size_t k = 0; k < bin; k++) {//loop over input rows associated with this output row + for(size_t i = 0; i < w; i++) {//loop over output columns + out[i] += std::accumulate(in, in + bin, 0.0f); + in += bin; + } + } + out += w; + } + } +} + +//@brief : in place bin a pattern (without changing its type, result with be averaged) +//@param in : raw buffer holding pattern (of pixel type T) +//@param out: location to write output (cannot overlap with input) +//@param bin: binning factor +//@param w : binned pattern width (input width is w * bin) +//@param h : binned pattern height (input height is h * bin) +template +void binAvg(T const* in, T* out, const size_t bin, const size_t w, const size_t h) { + if(0 == bin){ + throw std::runtime_error("bin factor must be positive"); + } else if(1 == bin) { + std::copy(in, in + w * h, out); + // std::transform(in, in + w * h, out, [](const T& i){return (float)i;});//handle trivial case + } else { + std::vector row(w);//space large enough to hold a single row + for(size_t j = 0; j < h; j++) {//loop over output rows + std::fill(row.begin(), row.end(), 0.0);//initialize output with zero + for(size_t k = 0; k < bin; k++) {//loop over input rows associated with this output row + for(size_t i = 0; i < w; i++) {//loop over output columns + row[i] += std::accumulate(in, in + bin, 0.0); + in += bin; + } + } + std::for_each(row.begin(), row.end(), [bin](double& v){v /= bin * bin;});//convert from sum to average + if(std::is_integral::value) std::for_each(row.begin(), row.end(), [](double& v){v = std::round(v);});//round to nearest int if needed + for(size_t i = 0; i < w; i++) out[i] = (T)row[i];//convert back to T + out += w; + } + } +} + +//@brief : in place flip a pattern +//@param buf: raw buffer holding pattern +//@param w : pattern width +//@param h : pattern height +//@param b : bytes per pixel +void flipPat(char*buf, const size_t w, const size_t h, const size_t b) { + const size_t rowBytes = w * b; + char* revBuf = buf + w * (h-1) * b;//pointer to start of last row + for(size_t j = 0; j < h/2; j++) { + std::swap_ranges(buf, buf + rowBytes, revBuf);//swap rows + buf += rowBytes;//increment lower row + revBuf -= rowBytes;//decremenet upper row + } +} + +int main(int argc, char *argv[]) { + const bool binToFloat = false;//should binned values be converted up to a float or kept as their current type + const bool flip = true;//should binned values be converted up to a float or kept as their current type + + //sanity check argument count + if(!(3 == argc || 4 == argc)) { + std::cout << "usage: " << argv[0] << " inputFile outputFile [binning]\n"; + std::cout << "\tinputFile - pattern file to read (*.up1, *.up2, *.data, or *.ebsp)\n"; + std::cout << "\toutputFile - output file (*.hdf)\n"; + std::cout << "\tbinning - [optional] binning size (must evenly divide into pattern size)\n"; + return EXIT_FAILURE; + } + + //prase arguments + size_t binning = 1; + std::string inputFile (argv[1]); + std::string outputFile(argv[2]); + if(argc == 4) { + if(!(std::istringstream(argv[3]) >> binning)) { + std::cout << "couldn't parse binning from 3rd argument `" << argv[3] << "'\n"; + return EXIT_FAILURE; + } + } + + //sanity check binning size + if(0 == binning) {//dont % 0 + std::cout << "binning must be a positive\n"; + return EXIT_FAILURE; + } + + //attempt to open pattern file (handles format via extension) + Timer t; + std::shared_ptr pats = emsphinx::ebsd::PatternFile::Read(inputFile); + if(emsphinx::ImageSource::Bits::UNK == pats->pixelType()) { + std::cout << "unknown pixel type\n"; + return EXIT_FAILURE; + } + + //now print some info + std::cout << "found " << pats->numPat() << " patterns in " << inputFile << ":\n"; + std::cout << "\twidth : " << pats->width () << '\n'; + std::cout << "\thegiht: " << pats->height() << '\n'; + std::cout << "\ttype : "; + switch(pats->pixelType()) { + case emsphinx::ImageSource::Bits::U8 : std::cout << "8 bit\n" ; break; + case emsphinx::ImageSource::Bits::U16: std::cout << "16 bit\n" ; break; + case emsphinx::ImageSource::Bits::F32: std::cout << "float\n" ; break; + case emsphinx::ImageSource::Bits::UNK: // intentional fall through + default: throw std::logic_error("unknown pixel type"); + } + std::cout << "\tbytes : " << pats->imBytes() << "\n"; + double mb = double(pats->imBytes() * pats->numPat()) / (1024*1024); + std::cout << "total size: "; + if(mb > 1024) { + std::cout << mb / 1024 << " GB\n"; + } else { + std::cout << mb << " MB\n"; + } + + //make sure our binning is valid + if(0 != (pats->width() % binning) || 0 != (pats->height() % binning)) { + std::cout << "binning doesn't evenly divide pattern size\n"; + return EXIT_FAILURE; + } + + std::cout << t.poll() << "s to parse pattern file\n"; + + //build up data type and space + H5::DataType dType; + switch(pats->pixelType()) {//determine data set type + case emsphinx::ImageSource::Bits::U8 : dType = H5::PredType::NATIVE_UINT8 ; break; + case emsphinx::ImageSource::Bits::U16: dType = H5::PredType::NATIVE_UINT16; break; + case emsphinx::ImageSource::Bits::F32: dType = H5::PredType::NATIVE_FLOAT ; break; + case emsphinx::ImageSource::Bits::UNK: // intentional fall through + default: throw std::logic_error("unknown pixel type"); + } + if(binToFloat) { + if(binning != 1) dType = H5::PredType::NATIVE_FLOAT;//if we're binning bin into floats + } + + hsize_t dims[3] = {pats->numPat(), pats->height() / binning, pats->width() / binning}; + H5::DataSpace dSpace(3, dims); + + //open output file and create dataset for patterns + H5::H5File hf(outputFile, H5F_ACC_TRUNC); + H5::DSetCreatPropList props; + props.setAllocTime(H5D_ALLOC_TIME_EARLY);//this lets us get a raw binary offset later with getOffset() + H5::DataSet dSet = hf.createDataSet("patterns", dType, dSpace, props); + + //create data spaces and arrays for hyperslab selection + dims[0] = 1;//buffer holds a single pattern + hsize_t count [3] = {1, dims[1], dims[2]};//block count + hsize_t start [3] = { 0, 0, 0};//block sizes + H5::DataSpace buffSpace(3, dims);//create H5 data space for memory buffer + + //finally loop over patterns writing into file + std::vector buff(pats->imBytes()); + if(1 == binning) {//no binning ==> raw copy + for(size_t i = 0; i < pats->numPat(); i++) { + start[0] = pats->extract(buff.data(), 1)[0];//read the next pattern and get index (shouldn't be empty since we're only extracting with one thread) + if(flip) flipPat(buff.data(), pats->width(), pats->height(), pats->pixBytes()); + dSpace.selectHyperslab(H5S_SELECT_SET, count, start);//tell hdf5 where to write + dSet.write((void*) buff.data(), dType, buffSpace, dSpace);//copy the pattern out + } + } else {//binning + std::vector binBuf(dims[1] * dims[2]);//this is big enough for either case + + if(binToFloat) { + switch(pats->pixelType()) { + case emsphinx::ImageSource::Bits::U8 : + for(size_t i = 0; i < pats->numPat(); i++) { + start[0] = pats->extract(buff.data(), 1)[0];//read the next pattern and get index (shouldn't be empty since we're only extracting with one thread) + if(flip) flipPat(buff.data(), pats->width(), pats->height(), pats->pixBytes()); + binFloat((uint8_t*) buff.data(), binBuf.data(), binning, dims[2], dims[1]); + dSpace.selectHyperslab(H5S_SELECT_SET, count, start);//tell hdf5 where to write + dSet.write((void*) binBuf.data(), H5::PredType::NATIVE_FLOAT, buffSpace, dSpace);//copy the pattern out + } + break; + + case emsphinx::ImageSource::Bits::U16: + for(size_t i = 0; i < pats->numPat(); i++) { + start[0] = pats->extract(buff.data(), 1)[0];//read the next pattern and get index (shouldn't be empty since we're only extracting with one thread) + if(flip) flipPat(buff.data(), pats->width(), pats->height(), pats->pixBytes()); + binFloat((uint16_t*) buff.data(), binBuf.data(), binning, dims[2], dims[1]); + dSpace.selectHyperslab(H5S_SELECT_SET, count, start);//tell hdf5 where to write + dSet.write((void*) binBuf.data(), H5::PredType::NATIVE_FLOAT, buffSpace, dSpace);//copy the pattern out + } + break; + + case emsphinx::ImageSource::Bits::F32: + for(size_t i = 0; i < pats->numPat(); i++) { + start[0] = pats->extract(buff.data(), 1)[0];//read the next pattern and get index (shouldn't be empty since we're only extracting with one thread) + if(flip) flipPat(buff.data(), pats->width(), pats->height(), pats->pixBytes()); + binFloat((float*) buff.data(), binBuf.data(), binning, dims[2], dims[1]); + dSpace.selectHyperslab(H5S_SELECT_SET, count, start);//tell hdf5 where to write + dSet.write((void*) binBuf.data(), H5::PredType::NATIVE_FLOAT, buffSpace, dSpace);//copy the pattern out + } + break; + + case emsphinx::ImageSource::Bits::UNK: // intentional fall through + default: throw std::logic_error("unknown pixel type"); + } + } else { + switch(pats->pixelType()) { + case emsphinx::ImageSource::Bits::U8 : + for(size_t i = 0; i < pats->numPat(); i++) { + start[0] = pats->extract(buff.data(), 1)[0];//read the next pattern and get index (shouldn't be empty since we're only extracting with one thread) + if(flip) flipPat(buff.data(), pats->width(), pats->height(), pats->pixBytes()); + binAvg((uint8_t *) buff.data(), (uint8_t *)binBuf.data(), binning, dims[2], dims[1]); + dSpace.selectHyperslab(H5S_SELECT_SET, count, start);//tell hdf5 where to write + dSet.write((void*) binBuf.data(), dType, buffSpace, dSpace);//copy the pattern out + } + break; + + case emsphinx::ImageSource::Bits::U16: + for(size_t i = 0; i < pats->numPat(); i++) { + start[0] = pats->extract(buff.data(), 1)[0];//read the next pattern and get index (shouldn't be empty since we're only extracting with one thread) + if(flip) flipPat(buff.data(), pats->width(), pats->height(), pats->pixBytes()); + binAvg((uint16_t*) buff.data(), (uint16_t*)binBuf.data(), binning, dims[2], dims[1]); + dSpace.selectHyperslab(H5S_SELECT_SET, count, start);//tell hdf5 where to write + dSet.write((void*) binBuf.data(), dType, buffSpace, dSpace);//copy the pattern out + } + break; + + case emsphinx::ImageSource::Bits::F32: + for(size_t i = 0; i < pats->numPat(); i++) { + start[0] = pats->extract(buff.data(), 1)[0];//read the next pattern and get index (shouldn't be empty since we're only extracting with one thread) + if(flip) flipPat(buff.data(), pats->width(), pats->height(), pats->pixBytes()); + binAvg((float *) buff.data(), (float *)binBuf.data(), binning, dims[2], dims[1]); + dSpace.selectHyperslab(H5S_SELECT_SET, count, start);//tell hdf5 where to write + dSet.write((void*) binBuf.data(), dType, buffSpace, dSpace);//copy the pattern out + } + break; + + case emsphinx::ImageSource::Bits::UNK: // intentional fall through + default: throw std::logic_error("unknown pixel type"); + } + } + } + std::cout << t.poll() << "s to repack patterns\n"; + return EXIT_SUCCESS; +} diff --git a/programs/sht2mp.cpp b/programs/sht2mp.cpp new file mode 100644 index 0000000..4260c2c --- /dev/null +++ b/programs/sht2mp.cpp @@ -0,0 +1,195 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include +#include + +#include "idx/master.hpp" +#include "sht_file.hpp" + +#define MINIZ_NO_STDIO +#define MINIZ_NO_TIME +#define MINIZ_NO_ZLIB_APIS +#include "miniz/miniz.c" + +//@brief : write a gray or rgb image to a png +//@param im : image to write +//@param w : image width +//@param h : image height +//@param samp: samples per pixel (1 for gray, 3 for rgb) +//@param name: file name to write to (*.png) +void writePng(uint8_t* im, const size_t w, const size_t h, const size_t samp, std::string name) { + //convert to png in memory + size_t pngSize = 0; + const mz_uint compr = MZ_BEST_COMPRESSION;//compression level [0,10] + const mz_bool flip = MZ_FALSE;//flip the image? + void *pPNG_data = tdefl_write_image_to_png_file_in_memory_ex((void*)im, (int)w, (int)h, (int)samp, &pngSize, compr, flip); + if(!pPNG_data) throw std::runtime_error("failed to create PNG image"); + + //write to file + std::ofstream os(name, std::ios::out | std::ios::binary); + os.write((char*)pPNG_data, pngSize); + mz_free(pPNG_data);//cleanup memory allocated by png creation +} + +int main(int argc, char *argv[]) { + try { + + //sanity check argument count + //this should be adjusted in the future to allow batch conversion since there is a good amount of overhead in building the transformer + if(4 != argc) { + std::cout << "usage: " << argv[0] << " inputFile nhFile shFile\n"; + std::cout << "\tinputFile - spherical hamrnoics file to write (*.Sht)\n"; + std::cout << "\tnhFile - location to write north hemisphere image (*.png)\n"; + std::cout << "\tshFile - location to write south hemisphere image (*.png)\n"; + return EXIT_FAILURE; + } + + //read in the master spectrum + emsphinx::MasterSpectra spec; + spec.read(argv[1]); + + std::cout << spec.getKv() << ' ' << spec.getSig() << '\n'; + + //build spherical harmonic transformer and reconstruct on square legendre grid + const size_t dim = spec.getBw() + (spec.getBw() % 2 == 0 ? 3 : 2); + emsphinx::square::DiscreteSHT sht(dim, spec.getBw(), emsphinx::square::Layout::Legendre); + std::vector sph(dim * dim * 2, 0.0); + sht.synthesize(spec.data(), sph.data(), sph.data() + dim * dim); + + //convert from doubles to 8 bit + std::pair minMax = std::minmax_element(sph.data(), sph.data() + sph.size()); + const double vMin = *minMax.first; + const double delt = *minMax.second - vMin; + const double fact = 255.0 / delt; + std::vector sph8(sph.size()); + std::transform(sph.begin(), sph.end(), sph8.begin(), [fact, vMin](const double& v) {return (uint8_t)std::round((v - vMin) * fact);}); + + //write outputs + writePng(sph8.data() , dim, dim, 1, argv[2]); + writePng(sph8.data() + dim * dim, dim, dim, 1, argv[3]); + + //now read in the raw SHT file and print header information + sht::File file; + std::ifstream is(argv[1], std::ios::in | std::ios::binary); + file.read(is); + + std::cout << "file version " << (int)file.header.fileVersion()[0] << '.' << (int)file.header.fileVersion()[1] << "\n"; + std::cout << "written with software version " << std::string(file.header.softwareVersion(), file.header.softwareVersion()+8) << "\n"; + std::cout << "notes : `" << file.header.notes << "'\n"; + std::cout << "doi : `" << file.header.doi << "'\n"; + + std::cout << "modality: "; + switch(file.header.modality()) { + case sht::Modality::Unknown: std::cout << "Unknown\n"; break; + case sht::Modality::EBSD : std::cout << "EBSD \n"; break; + case sht::Modality::ECP : std::cout << "ECP \n"; break; + case sht::Modality::TKD : std::cout << "TKD \n"; break; + case sht::Modality::PED : std::cout << "PED \n"; break; + case sht::Modality::Laue : std::cout << "Laue \n"; break; + default : std::cout << "invalid\n"; break; + + } + + std::cout << "vendor : "; + switch(file.header.vendor()) { + case sht::Vendor::Unknown: std::cout << "Unknown\n"; break; + case sht::Vendor::EMsoft : std::cout << "EMsoft \n"; break; + default : std::cout << "invalid\n"; break; + } + + std::cout << "beam eng: " << (int) file.header.beamEnergy() << '\n'; + std::cout << "angle 1 : " << (int) file.header.primaryAngle() << '\n'; + std::cout << "angle 2 : " << (int) file.header.secondaryAngle() << '\n'; + + std::cout << "rotations are " << (char) file.material.rotSense() << " with pijk = " << (int) file.material.pijk() << '\n'; + std::cout << "material has " << (int)file.material.numXtal() << " crystals with effective sg# " << (int)file.material.sgEff() << ":\n"; + for(const sht::CrystalData& xtal : file.material.xtals) { + std::cout << "\tsg " << (int) xtal.sgNum() << " setting " << (int) xtal.sgSet() << '\n'; + std::cout << "\t\taxis / cell choice: " << (int) xtal.sgAxis() << " / " << (int) xtal.sgCell() << "\n"; + std::cout << "\t\tadditional origin shift: " << xtal.oriX() << ", " << xtal.oriY() << ", " << xtal.oriZ() << "\n"; + std::cout << "\t\tabc: " << xtal.lat()[0] << ", " << xtal.lat()[1] << ", " << xtal.lat()[2] << "\n"; + std::cout << "\t\tabg: " << xtal.lat()[3] << ", " << xtal.lat()[4] << ", " << xtal.lat()[5] << "\n"; + std::cout << "\t\trot: " << xtal.rot()[0] << ", " << xtal.rot()[1] << ", " << xtal.rot()[2] << ", " << xtal.rot()[3] << "\n"; + std::cout << "\t\twgt: " << xtal.weight() << '\n'; + std::cout << "\t\t" << (int) xtal.numAtoms() << ":\n"; + + for(const sht::AtomData& at : xtal.atoms) { + std::cout << "\t\t\t" << (int) at.atZ() << ": " << at.x() / 24.0f << ' ' << at.y() / 24.0f << ' ' << at.z() / 24.0f << ' ' << at.occ() << ' ' << at.debWal() << '\n'; + } + } + + if(NULL != file.simulMeta.get()) { + std::cout << "has simulation data\n"; + if(sht::Vendor::EMsoft == file.header.vendor () && + sht::Modality::EBSD == file.header.modality() && + 80 == file.simulMeta->size()) { + sht::EMsoftED* pED = (sht::EMsoftED*) file.simulMeta.get(); + + + std::cout << "\tsigStart : " << pED->sigStart () << '\n'; + std::cout << "\tsigEnd : " << pED->sigEnd () << '\n'; + std::cout << "\tsigStep : " << pED->sigStep () << '\n'; + std::cout << "\tomega : " << pED->omega () << '\n'; + std::cout << "\tkeV : " << pED->keV () << '\n'; + std::cout << "\teHistMin : " << pED->eHistMin () << '\n'; + std::cout << "\teBinSize : " << pED->eBinSize () << '\n'; + std::cout << "\tdepthMax : " << pED->depthMax () << '\n'; + std::cout << "\tdepthStep: " << pED->depthStep() << '\n'; + std::cout << "\tthickness: " << pED->thickness() << '\n'; + std::cout << "\ttotNumEl : " << pED->totNumEl () << '\n'; + std::cout << "\tnumSx : " << pED->numSx () << '\n'; + std::cout << "\tc1 : " << pED->c1 () << '\n'; + std::cout << "\tc2 : " << pED->c2 () << '\n'; + std::cout << "\tc3 : " << pED->c3 () << '\n'; + std::cout << "\tsigDbDiff: " << pED->sigDbDiff() << '\n'; + std::cout << "\tdMin : " << pED->dMin () << '\n'; + std::cout << "\tnumPx : " << pED->numPx () << '\n'; + std::cout << "\tlatGridType: "; + switch( pED->latGridType()) { + case 1 : std::cout << "square lambert\n";break; + case 2 : std::cout << "square legendre\n";break; + default: std::cout << "unknown\n";break; + } + } + } + + + } catch(std::exception& e) { + std::cout << e.what() << '\n'; + } + + return EXIT_SUCCESS; +} diff --git a/programs/sht_wisdom.cpp b/programs/sht_wisdom.cpp new file mode 100644 index 0000000..7e5b47c --- /dev/null +++ b/programs/sht_wisdom.cpp @@ -0,0 +1,72 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +#include "util/fft.hpp"//for ffts in DiscreteSHT + +int main(int argc, char *argv[]) { + //parse arguments (file names / bandwidth) + typedef double Real; + if(2 != argc) { + std::cout << "usage: " << argv[0] << "bandWidth\n"; + std::cout << "\tbandWidth : max bandwidth to build FFTW wisdom fore\n"; + std::cout << "\t some reasonable values are 63, 95, 158, 263\n"; + return EXIT_FAILURE; + } + const size_t bw = std::strtoul(argv[1], NULL, 0); + + //start by building wisdom for all spherical grid sizes up to the size for bw + const size_t Nt = bw / 2 + 2;//number of rings in largest bandwidth grid + for(size_t y = 0; y < Nt; y++) { + std::cout << "\rplanning 1D fft for ring " << y+1 << "/" << Nt; + std::cout.flush(); + fft::RealFFT plan(std::max(1, 8 * y), fft::flag::Plan::Patient); + } + std::cout << '\n'; + + //now build some fast sizes + static const size_t FastSize[27] = { + 25, 32, 38, 41, 53, 63, 68, 74, 88, 95, 113, 122, 123, 158, 172, 188, 203, 221, 263, 284, 313, 338, 365, 368, 438, 473, 515 + }; + for(size_t i = 0; i < 27; i++) { + if(FastSize[i] > bw) break; + std::cout << "\rplanning 3D fft for BW = " << FastSize[i]; + std::cout.flush(); + fft::SepRealFFT3D plan(FastSize[i], fft::flag::Plan::Patient); + } + std::cout << '\n'; + + return 0; +} diff --git a/scripts/gen_bw.cpp b/scripts/gen_bw.cpp new file mode 100644 index 0000000..13cae68 --- /dev/null +++ b/scripts/gen_bw.cpp @@ -0,0 +1,88 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include + +//@brief : compute the base 2 log of a number +//@param v: integer to compute base 2 log of +//@return : floor(log(v, 2)) +//@note : from stanford bit twiddles https://graphics.stanford.edu/~seander/bithacks.html#IntegerLog +uint32_t log2(uint32_t v) { + uint32_t r = (v > 0x0000FFFF) << 4; v >>= r; + uint32_t s = (v > 0x000000FF) << 3; v >>= s; r |= s ; + s = (v > 0x0000000F) << 2; v >>= s; r |= s ; + s = (v > 0x00000003) << 1; v >>= s; r |= s ; + r |= (v >> 1); + return r; +} + +int main() { + //specify the largest number we're interested and compute its base 2 log + const uint32_t lb = 25 ;//lower bound on meaningful bandwidths + const uint32_t ub = 2000;//upper bound on fft sizes + const uint32_t l2 = log2(ub);//2^l2 is the largest value to consider + + //build initial set of small primes that FFTW reccomends + std::set factors; + factors.insert(1); + factors.insert(2); + factors.insert(3); + factors.insert(5); + factors.insert(7); + + //build set of products of small primes (these are good fft sizes) + for(uint32_t m = 0; m < l2; m++) { + std::set products; + for(const uint32_t i : factors) { + for(const uint32_t j : factors) { + const uint32_t p = i * j; + if(p <= ub) { + products.insert(p); + } + } + } + factors = products; + } + + //factors now contains all products of small primes <= ub, print valid bandwidths + for(const uint32_t i : factors) {//loop over valid fft sizes + if(1 == i % 2) {//check if there is a bandwidth such that 2 * bw - 1 == i + const uint32_t bw = (i + 1) / 2; + if(bw >= lb) std::cout << bw << '\n'; + } + } + + return 0; +} diff --git a/scripts/generator_table.cpp b/scripts/generator_table.cpp new file mode 100644 index 0000000..75c80cf --- /dev/null +++ b/scripts/generator_table.cpp @@ -0,0 +1,329 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +//@brief : function to undo encoding of generator string into 64bit int +//@param v: generator string encoded as 64 bit integer +//@param s: location to write generator string (35 characters = 34 + NULL) +//@note : look up table generated with this program +void decodeLut(const uint64_t v, char * s) { + //look up table for 4 character generator code substrings + static const char lut[88][5] = { + " ", "1BBB", "1BBO", "1OBB", "1OBZ", "1OYZ", "1XXX", "1YBO", + "1YBY", "1YYO", "1YYY", "1ZZZ", "aDDD", "aDDO", "aDOD", "aECC", + "aODD", "bDDD", "bDDO", "bDOD", "bDOO", "bODD", "bODO", "bOOD", + "bOOO", "cDDB", "cDDD", "cDDF", "cDDO", "cDOB", "cDOD", "cDOF", + "cODD", "cODO", "cOOD", "cOOO", "dOOO", "eBFF", "eDDD", "eFBB", + "eFBF", "eOOC", "eOOD", "eOOE", "eOOO", "fDDD", "fOOC", "fOOD", + "fOOE", "fOOO", "gDDB", "gDDD", "gDDF", "gDDO", "gODB", "gOOB", + "gOOD", "gOOF", "gOOO", "hBBB", "hDDD", "hDDO", "hFFF", "hODB", + "hODD", "iOOD", "iOOO", "jBBB", "jDDD", "jDDO", "jDOD", "jDOO", + "jODD", "jODO", "jOOD", "jOOO", "kOOD", "kOOO", "lBBB", "lDDD", + "lOOD", "lOOO", "mOOO", "nOOC", "nOOE", "nOOO" + }; + + //break apart bytes + uint8_t bytes[8] = { + (uint8_t) ((v >> 56) & 0x7F), + (uint8_t) ((v >> 48) & 0x7F), + (uint8_t) ((v >> 40) & 0x7F), + (uint8_t) ((v >> 32) & 0x7F), + (uint8_t) ((v >> 24) & 0x7F), + (uint8_t) ((v >> 16) & 0x7F), + (uint8_t) ((v >> 8) & 0x7F), + (uint8_t) ((v ) & 0x7F), + }; + + //convert bytes to strings and count valid number + int count = 0; + bool hasAlt = false; + for(int i = 0; i < 8; i++) { + char* p = s + 2 + i * 4;//get pointer to sequence start + for(int j = 0; j < 4; j++) p[j] = lut[bytes[i]][j];//copy string + if(0x00 != bytes[i]) ++count;//accumulate number of generators + if('1' == lut[bytes[i]][0]) {//check for an alternate origin + if(hasAlt) {//this shouldn't be possible + for(int i = 0; i < 34; i++) s[i] = 0;//clear string + return; + } + hasAlt = true;//flag alternate origin + --count;//doun't include in count + } + } + + //set inversion flag + s[0] = 0x8000000000000000 & v ? '1' : '0'; + + //set count + s[1] = '0' + count; + + //append with 0 if needed + if(!hasAlt) s[2 + 4 * count] = '0'; +} + +//@brief: look up table for generators for each space group such that decodeLut(SGLut[i+1], s) writes the generator string for space group i in s +//@note : generated with this program +static const uint64_t SGLut[237] = { + 0x0000000000000000,0x8000000000000000,0x2300000000000000,0x2100000000000000,0x0d23000000000000,0x4b00000000000000, + 0x4a00000000000000,0x0d4b000000000000,0x0d4a000000000000,0xa300000000000000,0xa100000000000000,0x8d23000000000000, + 0xa200000000000000,0xa000000000000000,0x8d22000000000000,0x1823000000000000,0x1722000000000000,0x181c000000000000, + 0x1320000000000000,0x0d17220000000000,0x0d18230000000000,0x100e182300000000,0x0c18230000000000,0x0c13200000000000, + 0x184b000000000000,0x174a000000000000,0x184a000000000000,0x1847000000000000,0x1747000000000000,0x1848000000000000, + 0x1346000000000000,0x1845000000000000,0x1745000000000000,0x1844000000000000,0x0d184b0000000000,0x0d174a0000000000, + 0x0d184a0000000000,0x10184b0000000000,0x1018490000000000,0x1018470000000000,0x1018450000000000,0x100e184b00000000, + 0x100e184300000000,0x0c184b0000000000,0x0c18450000000000,0x0c18470000000000,0x9823000000000000,0x18233c0100000000, + 0x9822000000000000,0x18233d0200000000,0x9423000000000000,0x941a000000000000,0x931e000000000000,0x9422000000000000, + 0x981c000000000000,0x9220000000000000,0x9720000000000000,0x981a000000000000,0x181c3d0200000000,0x9122000000000000, + 0x9320000000000000,0x9321000000000000,0x8d17220000000000,0x8d15200000000000,0x8d18230000000000,0x8d18220000000000, + 0x8d16210000000000,0x0d12234003000000,0x900e182300000000,0x100e18233b0b0000,0x8c18230000000000,0x8c181c0000000000, + 0x8c13200000000000,0x8c16210000000000,0x183a000000000000,0x1737000000000000,0x1838000000000000,0x1739000000000000, + 0x0c183a0000000000,0x0c11360000000000,0x1852000000000000,0x0c18520000000000,0x983a000000000000,0x9838000000000000, + 0x18353d0700000000,0x18333c0a00000000,0x8c183a0000000000,0x0c11363f05000000,0x183a230000000000,0x18351c0000000000, + 0x1737230000000000,0x1732190000000000,0x1838230000000000,0x18331a0000000000,0x1739230000000000,0x17341b0000000000, + 0x0c183a2300000000,0x0c11361f00000000,0x183a4b0000000000,0x183a450000000000,0x18384a0000000000,0x1833440000000000, + 0x183a4a0000000000,0x183a440000000000,0x18384b0000000000,0x1838450000000000,0x0c183a4b00000000,0x0c183a4a00000000, + 0x0c11364b00000000,0x0c11364a00000000,0x1852230000000000,0x1852220000000000,0x18521c0000000000,0x18521a0000000000, + 0x18524b0000000000,0x18524a0000000000,0x1852450000000000,0x1852440000000000,0x0c18524b00000000,0x0c18524a00000000, + 0x0c18522300000000,0x0c18521f00000000,0x983a230000000000,0x983a220000000000,0x183a233d09000000,0x183a233c0a000000, + 0x983a1c0000000000,0x983a1a0000000000,0x18351c3d07000000,0x18351a3d07000000,0x9838230000000000,0x9838220000000000, + 0x1833223c08000000,0x1833233c08000000,0x98381c0000000000,0x98331a0000000000,0x18331a3c08000000,0x18331c3c08000000, + 0x8c183a2300000000,0x8c183a2200000000,0x0c11361f3f040000,0x0c11361d3f040000,0x5500000000000000,0x5300000000000000, + 0x5400000000000000,0x0f55000000000000,0xd500000000000000,0x8f55000000000000,0x5531000000000000,0x552c000000000000, + 0x5330000000000000,0x532c000000000000,0x542e000000000000,0x542c000000000000,0x0f552c0000000000,0x554d000000000000, + 0x5551000000000000,0x554c000000000000,0x5550000000000000,0x0f554d0000000000,0x0f554c0000000000,0xd531000000000000, + 0xd52f000000000000,0xd52c000000000000,0xd52a000000000000,0x8f552c0000000000,0x8f552a0000000000,0x5518000000000000, + 0x5317000000000000,0x5417000000000000,0x5418000000000000,0x5318000000000000,0x5517000000000000,0x5542000000000000, + 0xd518000000000000,0xd517000000000000,0x55182c0000000000,0x5317290000000000,0x54172b0000000000,0x54182b0000000000, + 0x5318290000000000,0x55172c0000000000,0x55184d0000000000,0x55184c0000000000,0x55174c0000000000,0x55174d0000000000, + 0x55424d0000000000,0x55414c0000000000,0x55422c0000000000,0x55412c0000000000,0xd5182c0000000000,0xd5182a0000000000, + 0xd5172a0000000000,0xd5172c0000000000,0x1823240000000000,0x100e182324000000,0x0c18232400000000,0x1320240000000000, + 0x0c13202400000000,0x9823240000000000,0x1823243c0a000000,0x900e182324000000,0x100e1823243b0b00,0x8c18232400000000, + 0x9320240000000000,0x8c13202400000000,0x1823242c00000000,0x1823242600000000,0x100e1823242c0000,0x100e151c24280000, + 0x0c1823242c000000,0x1320242500000000,0x1320242700000000,0x0c13202427000000,0x1823245100000000,0x100e182324510000, + 0x0c18232451000000,0x1823244f00000000,0x100e1823244f0000,0x0c1320244e000000,0x9823242c00000000,0x1823242c3c0a0000, + 0x9823242600000000,0x182324263c0a0000,0x900e1823242c0000,0x900e182324260000,0x100e151c24283b0b,0x100e151c24283e06, + 0x8c1823242c000000,0x8c13202427000000,0x2400000000000000,0xa400000000000000,0x2431000000000000,0x2451000000000000, + 0x244f000000000000,0xa431000000000000,0xa42d000000000000 +}; + +#include +#include + +//the original space group generator strings +const std::vector generators = { + "000 ", "100 ", "01cOOO0 ", + "01cODO0 ", "02aDDOcOOO0 ", "01jOOO0 ", + "01jOOD0 ", "02aDDOjOOO0 ", "02aDDOjOOD0 ", + "11cOOO0 ", "11cODO0 ", "12aDDOcOOO0 ", + "11cOOD0 ", "11cODD0 ", "12aDDOcOOD0 ", + "02bOOOcOOO0 ", "02bOODcOOD0 ", "02bOOOcDDO0 ", + "02bDODcODD0 ", "03aDDObOODcOOD0 ", "03aDDObOOOcOOO0 ", + "04aODDaDODbOOOcOOO0 ", "03aDDDbOOOcOOO0 ", "03aDDDbDODcODD0 ", + "02bOOOjOOO0 ", "02bOODjOOD0 ", "02bOOOjOOD0 ", + "02bOOOjDOO0 ", "02bOODjDOO0 ", "02bOOOjODD0 ", + "02bDODjDOD0 ", "02bOOOjDDO0 ", "02bOODjDDO0 ", + "02bOOOjDDD0 ", "03aDDObOOOjOOO0 ", "03aDDObOODjOOD0 ", + "03aDDObOOOjOOD0 ", "03aODDbOOOjOOO0 ", "03aODDbOOOjODO0 ", + "03aODDbOOOjDOO0 ", "03aODDbOOOjDDO0 ", "04aODDaDODbOOOjOOO0 ", + "04aODDaDODbOOOjBBB0 ", "03aDDDbOOOjOOO0 ", "03aDDDbOOOjDDO0 ", + "03aDDDbOOOjDOO0 ", "12bOOOcOOO0 ", "03bOOOcOOOhDDD1BBB ", + "12bOOOcOOD0 ", "03bOOOcOOOhDDO1BBO ", "12bDOOcOOO0 ", + "12bDOOcDDD0 ", "12bDODcDOD0 ", "12bDOOcOOD0 ", + "12bOOOcDDO0 ", "12bDDOcODD0 ", "12bOODcODD0 ", + "12bOOOcDDD0 ", "03bOOOcDDOhDDO1BBO ", "12bDDDcOOD0 ", + "12bDODcODD0 ", "12bDODcODO0 ", "13aDDObOODcOOD0 ", + "13aDDObODDcODD0 ", "13aDDObOOOcOOO0 ", "13aDDObOOOcOOD0 ", + "13aDDObODOcODO0 ", "04aDDObDDOcOOOhODD1OBB ", "14aODDaDODbOOOcOOO0 ", + "05aODDaDODbOOOcOOOhBBB1ZZZ ", "13aDDDbOOOcOOO0 ", "13aDDDbOOOcDDO0 ", + "13aDDDbDODcODD0 ", "13aDDDbODOcODO0 ", "02bOOOgOOO0 ", + "02bOODgOOB0 ", "02bOOOgOOD0 ", "02bOODgOOF0 ", + "03aDDDbOOOgOOO0 ", "03aDDDbDDDgODB0 ", "02bOOOmOOO0 ", + "03aDDDbOOOmOOO0 ", "12bOOOgOOO0 ", "12bOOOgOOD0 ", + "03bOOOgDDOhDDO1YBO ", "03bOOOgDDDhDDD1YYY ", "13aDDDbOOOgOOO0 ", + "04aDDDbDDDgODBhODB1OYZ ", "03bOOOgOOOcOOO0 ", "03bOOOgDDOcDDO0 ", + "03bOODgOOBcOOO0 ", "03bOODgDDBcDDB0 ", "03bOOOgOODcOOO0 ", + "03bOOOgDDDcDDD0 ", "03bOODgOOFcOOO0 ", "03bOODgDDFcDDF0 ", + "04aDDDbOOOgOOOcOOO0 ", "04aDDDbDDDgODBcDOF0 ", "03bOOOgOOOjOOO0 ", + "03bOOOgOOOjDDO0 ", "03bOOOgOODjOOD0 ", "03bOOOgDDDjDDD0 ", + "03bOOOgOOOjOOD0 ", "03bOOOgOOOjDDD0 ", "03bOOOgOODjOOO0 ", + "03bOOOgOODjDDO0 ", "04aDDDbOOOgOOOjOOO0 ", "04aDDDbOOOgOOOjOOD0 ", + "04aDDDbDDDgODBjOOO0 ", "04aDDDbDDDgODBjOOD0 ", "03bOOOmOOOcOOO0 ", + "03bOOOmOOOcOOD0 ", "03bOOOmOOOcDDO0 ", "03bOOOmOOOcDDD0 ", + "03bOOOmOOOjOOO0 ", "03bOOOmOOOjOOD0 ", "03bOOOmOOOjDDO0 ", + "03bOOOmOOOjDDD0 ", "04aDDDbOOOmOOOjOOO0 ", "04aDDDbOOOmOOOjOOD0 ", + "04aDDDbOOOmOOOcOOO0 ", "04aDDDbOOOmOOOcDOF0 ", "13bOOOgOOOcOOO0 ", + "13bOOOgOOOcOOD0 ", "04bOOOgOOOcOOOhDDO1YYO ", "04bOOOgOOOcOOOhDDD1YYY ", + "13bOOOgOOOcDDO0 ", "13bOOOgOOOcDDD0 ", "04bOOOgDDOcDDOhDDO1YBO ", + "04bOOOgDDOcDDDhDDO1YBO ", "13bOOOgOODcOOO0 ", "13bOOOgOODcOOD0 ", + "04bOOOgDDDcOODhDDD1YBY ", "04bOOOgDDDcOOOhDDD1YBY ", "13bOOOgOODcDDO0 ", + "13bOOOgDDDcDDD0 ", "04bOOOgDDDcDDDhDDD1YBY ", "04bOOOgDDDcDDOhDDD1YBY ", + "14aDDDbOOOgOOOcOOO0 ", "14aDDDbOOOgOOOcOOD0 ", "05aDDDbDDDgODBcDOFhODB1OBZ ", + "05aDDDbDDDgODBcDOBhODB1OBZ ", "01nOOO0 ", "01nOOC0 ", + "01nOOE0 ", "02aECCnOOO0 ", "11nOOO0 ", + "12aECCnOOO0 ", "02nOOOfOOO0 ", "02nOOOeOOO0 ", + "02nOOCfOOE0 ", "02nOOCeOOO0 ", "02nOOEfOOC0 ", + "02nOOEeOOO0 ", "03aECCnOOOeOOO0 ", "02nOOOkOOO0 ", + "02nOOOlOOO0 ", "02nOOOkOOD0 ", "02nOOOlOOD0 ", + "03aECCnOOOkOOO0 ", "03aECCnOOOkOOD0 ", "12nOOOfOOO0 ", + "12nOOOfOOD0 ", "12nOOOeOOO0 ", "12nOOOeOOD0 ", + "13aECCnOOOeOOO0 ", "13aECCnOOOeOOD0 ", "02nOOObOOO0 ", + "02nOOCbOOD0 ", "02nOOEbOOD0 ", "02nOOEbOOO0 ", + "02nOOCbOOO0 ", "02nOOObOOD0 ", "02nOOOiOOO0 ", + "12nOOObOOO0 ", "12nOOObOOD0 ", "03nOOObOOOeOOO0 ", + "03nOOCbOODeOOC0 ", "03nOOEbOODeOOE0 ", "03nOOEbOOOeOOE0 ", + "03nOOCbOOOeOOC0 ", "03nOOObOODeOOO0 ", "03nOOObOOOkOOO0 ", + "03nOOObOOOkOOD0 ", "03nOOObOODkOOD0 ", "03nOOObOODkOOO0 ", + "03nOOOiOOOkOOO0 ", "03nOOOiOODkOOD0 ", "03nOOOiOOOeOOO0 ", + "03nOOOiOODeOOO0 ", "13nOOObOOOeOOO0 ", "13nOOObOOOeOOD0 ", + "13nOOObOODeOOD0 ", "13nOOObOODeOOO0 ", "03bOOOcOOOdOOO0 ", + "05aODDaDODbOOOcOOOdOOO0 ", "04aDDDbOOOcOOOdOOO0 ", "03bDODcODDdOOO0 ", + "04aDDDbDODcODDdOOO0 ", "13bOOOcOOOdOOO0 ", "04bOOOcOOOdOOOhDDD1YYY ", + "15aODDaDODbOOOcOOOdOOO0 ", "06aODDaDODbOOOcOOOdOOOhBBB1ZZZ ", "14aDDDbOOOcOOOdOOO0 ", + "13bDODcODDdOOO0 ", "14aDDDbDODcODDdOOO0 ", "04bOOOcOOOdOOOeOOO0 ", + "04bOOOcOOOdOOOeDDD0 ", "06aODDaDODbOOOcOOOdOOOeOOO0 ", "06aODDaDODbODDcDDOdOOOeFBF0 ", + "05aDDDbOOOcOOOdOOOeOOO0 ", "04bDODcODDdOOOeBFF0 ", "04bDODcODDdOOOeFBB0 ", + "05aDDDbDODcODDdOOOeFBB0 ", "04bOOOcOOOdOOOlOOO0 ", "06aODDaDODbOOOcOOOdOOOlOOO0 ", + "05aDDDbOOOcOOOdOOOlOOO0 ", "04bOOOcOOOdOOOlDDD0 ", "06aODDaDODbOOOcOOOdOOOlDDD0 ", + "05aDDDbDODcODDdOOOlBBB0 ", "14bOOOcOOOdOOOeOOO0 ", "05bOOOcOOOdOOOeOOOhDDD1YYY ", + "14bOOOcOOOdOOOeDDD0 ", "05bOOOcOOOdOOOeDDDhDDD1YYY ", "16aODDaDODbOOOcOOOdOOOeOOO0 ", + "16aODDaDODbOOOcOOOdOOOeDDD0 ", "07aODDaDODbODDcDDOdOOOeFBFhBBB1ZZZ", "07aODDaDODbODDcDDOdOOOeFBFhFFF1XXX", + "15aDDDbOOOcOOOdOOOeOOO0 ", "15aDDDbDODcODDdOOOeFBB0 ", "01dOOO0 ", + "11dOOO0 ", "02dOOOfOOO0 ", "02dOOOlOOO0 ", + "02dOOOlDDD0 ", "12dOOOfOOO0 ", "12dOOOfDDD0 " +}; + +#include +#include +#include + +int main() { + //space to hold the possible 4 character generator substrings e.g. "hDDD" + std::set lib; + lib.insert(" ");//empty space + + //counter for maximum number of 4 character sub strings across all generators + size_t maxNum = 0; + + //loop over generators + for(const std::string& gen : generators) { + //get number of generator matricies + size_t num = 0; + switch(gen[1]) { + case '0': num = 0; break; + case '1': num = 1; break; + case '2': num = 2; break; + case '3': num = 3; break; + case '4': num = 4; break; + case '5': num = 5; break; + case '6': num = 6; break; + case '7': num = 7; break; + case '8': num = 8; break; + case '9': num = 9; break; + default: throw std::runtime_error("expected number in 2nd character of generator string"); + } + + //check if there is a special origin shift generator + if('1' == gen[2 + 4 * num]) ++num; + if(num > maxNum) maxNum = num; + + //now accumulate all 4 letter substrings into library + for(size_t i = 0; i < num; i++) lib.insert(std::string(gen.cbegin() + 2 + i * 4, gen.cbegin() + 6 + i * 4)); + } + + //make sure we can still nicely fit everything into a single 64 bit integer (in case some weird QC are added later) + //at the time of writing there are a maximum of 8 matricies per space group + 86 possibilities so we can nicely convert to 8*8 bits + //we actually need to fit into 7 bits so there is space for the inversion flag in the first bit + //that are still 7 unused bits if needed (first bit of each byte after the first) + if(lib.size() > 128 || maxNum > 8) throw std::runtime_error("cannot convert to 64bit ints"); + + //print the library lookup table + std::cout << "static const char lut[" << lib.size() << "][5] = {"; + size_t count = 0; + for(const std::string& s : lib) { + if(0 == count++ % 8) std::cout << "\n\t"; + std::cout << '"' << s << "\", "; + } + std::cout << "\n};\n"; + + //now loop over space groups again building the space group table + std::vector lut; + for(const std::string& gen : generators) { + //get number of generator matricies + size_t num = 0; + switch(gen[1]) { + case '0': num = 0; break; + case '1': num = 1; break; + case '2': num = 2; break; + case '3': num = 3; break; + case '4': num = 4; break; + case '5': num = 5; break; + case '6': num = 6; break; + case '7': num = 7; break; + case '8': num = 8; break; + case '9': num = 9; break; + //we shouldn't need a default here since it would have thrown above + } + + //check if there is a special origin shift generator + if('1' == gen[2 + 4 * num]) ++num; + + //now loop over all 4 letter substrings + uint64_t encoded = 0; + for(size_t i = 0; i < num; i++) { + std::string sub(gen.cbegin() + 2 + i * 4, gen.cbegin() + 6 + i * 4);//break out 4 character substring + encoded |= std::distance(lib.begin(), lib.find(sub)) << (56 - 8 * i); + } + + //save inversion flag + if('1' == gen[0]) encoded |= (uint64_t(1) << 63); + + //make sure we can resconstruct the string + std::string rec(".................................."); + decodeLut(encoded, &rec[0]); + if(rec != gen) throw std::runtime_error("couldn't round trip decode " + gen); + + //save encoded value + lut.push_back(encoded); + } + + //print the space group lookup table + std::cout << std::hex << std::setfill('0'); + std::cout << "static const uint64_t SGLut[" << lut.size() << "] = {"; + for(size_t i = 0; i < lut.size(); i++) { + if(0 == i % 6) std::cout << "\n\t"; + std::cout << "0x" << std::setw(16) << lut[i] << ','; + } + std::cout << "\n};\n"; +} diff --git a/scripts/master_sphere.py b/scripts/master_sphere.py new file mode 100644 index 0000000..01906be --- /dev/null +++ b/scripts/master_sphere.py @@ -0,0 +1,162 @@ +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # +# Copyright (c) 2018, Marc De Graef Research Group/Carnegie Mellon University # +# All rights reserved. # +# # +# Author William C. Lenthe # +# # +# Redistribution and use in source and binary forms, with or without modification, are # +# permitted provided that the following conditions are met: # +# # +# - Redistributions of source code must retain the above copyright notice, this list # +# of conditions and the following disclaimer. # +# - Redistributions in binary form must reproduce the above copyright notice, this # +# list of conditions and the following disclaimer in the documentation and/or # +# other materials provided with the distribution. # +# - Neither the names of Marc De Graef, Carnegie Mellon University nor the names # +# of its contributors may be used to endorse or promote products derived from # +# this software without specific prior written permission. # +# # +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE # +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # +# # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +#@brief : construct a spherical surface mesh of a master pattern for paraview visualization +#@param inputFile : EMsoft master pattern file (.h5) +#@param outputFile: prefix for output file names (outputFile.h5 and outputFile.xdmf) +#@note : drag outputFile.xdmf into paraview and select 'Xdmf Reader' (not 'Xdmf3 Reader') + +import h5py +import numpy as np + +inputFile = 'Fo-master.h5' # EMsoft output file +outputFile = 'output' # what should we name the created h5/xdmf file + +# inverse square lambert projection (square (-1 <= x,y <= 1) to unit sphere) +def equalAreaSquareToSphere(X, Y): + # restrict to +/-1 box and determine largest absolute value coordinate + X = np.clip(X, -1, 1) + Y = np.clip(Y, -1, 1) + aX = np.abs(X) + aY = np.abs(Y) + vMax = np.maximum(aX, aY) + + # compute x and y + X0 = np.where(X == 0, 1, X) # don't divide by 0 + Y0 = np.where(Y == 0, 1, Y) # don't divide by 0 + q1 = Y * np.sqrt(2 - Y * Y) # aX <= aY + q2 = X * np.sqrt(2 - X * X) # aX > aY + qq1 = (X * np.pi) / (Y0 * 4) # aX <= aY + qq2 = (Y * np.pi) / (X0 * 4) # aX > aY + xyz = np.zeros(X.shape + (3,)) + xyz[...,0] = np.where(aX <= aY, q1 * np.sin(qq1), q2 * np.cos(qq2)) + xyz[...,1] = np.where(aX <= aY, q1 * np.cos(qq1), q2 * np.sin(qq2)) + + # compute z and normalize + xyz[...,2] = 1 - vMax * vMax + mag = np.linalg.norm(xyz, axis = -1) + xyz[...,0] /= mag + xyz[...,1] /= mag + xyz[...,2] /= mag + return xyz; + +# read mc and crystal data +f = h5py.File(inputFile) +oc = f['/CrystalData/AtomData'][3] # read occupancies +en = np.sum(f['/EMData/MCOpenCL/accum_e'], axis=(0,1)).astype('float32') + +# read master patterns for each atom and accumulate occupancy weighted value +nh = f['/EMData/EBSDmaster/mLPNH'][0] * oc[0] +sh = f['/EMData/EBSDmaster/mLPSH'][0] * oc[0] +for i in range(1, len(oc)): + nh += f['/EMData/EBSDmaster/mLPNH'][i] * oc[i] + sh += f['/EMData/EBSDmaster/mLPSH'][i] * oc[i] + +# convert energy bins to percentages and compute weighted average of patterns +en = en / np.sum(en) +nh = np.average(nh, axis = 0, weights = en) +sh = np.average(sh, axis = 0, weights = en) + +# compute back projected coordinates of each point and vectorize +X = np.linspace(-1, 1, nh.shape[1]).astype('float32') +Y = np.linspace(-1, 1, nh.shape[0]).astype('float32') +[X, Y] = np.meshgrid(X, Y); +xyz = equalAreaSquareToSphere(X, Y) +xyz = np.reshape(xyz, (nh.shape[0]*nh.shape[1], 3))#(x,y,3) -> (x*y,3) + +# build quad mesh (squares in ccw order from top left as v0, v1, v2, v3) +v0 = np.tile(np.arange(0, nh.shape[1]-1, 1, dtype = 'uint32'), nh.shape[0]-1) # 0, 1, 2, ..., x-2, x-1, 0, 1, 2, ... y-1 times +v0 = v0 + np.repeat(np.arange(0, nh.shape[0]-1, 1), nh.shape[1]-1) * nh.shape[1] # indicies of all points except for last row/col in row major order +v1 = v0 + nh.shape[1] +v2 = v1 + 1 +v3 = v0 + 1 + +# compute shortest diagonal of each quad and split into trangles +d02 = np.linalg.norm(xyz[v0] - xyz[v2], axis = -1) +d13 = np.linalg.norm(xyz[v1] - xyz[v3], axis = -1) +use02 = d02 <= d13 +tris = np.empty((v0.shape[0], 6), dtype = 'uint32') +tris[:,0] = v0 +tris[:,1] = v1 +tris[:,2] = np.where(use02, v2, v3) +tris[:,3] = v2 +tris[:,4] = v3 +tris[:,5] = np.where(use02, v0, v1) +tris = tris.reshape((v0.shape[0] * 2, 3)) + +# add southern hemisphere +trisSh = np.empty_like(tris) +trisSh[:,0], trisSh[:,1], trisSh[:,2] = tris[:,0], tris[:,2], tris[:,1] # flip winding +trisSh = trisSh + xyz.shape[0] +xyzSh = np.copy(xyz) +xyzSh[:,2] = -xyzSh[:,2] # move to southern hemisphere +tris = np.concatenate((tris, trisSh)) +xyz = np.concatenate((xyz, xyzSh)) + +# write mesh to hdf5 +f = h5py.File(outputFile + '.h5', 'w') +f['/verts'] = xyz +f['/tris'] = tris +f['/scalar'] = np.concatenate((nh.reshape((nh.shape[0] * nh.shape[1])), sh.reshape((sh.shape[0] * sh.shape[1])))) + +# write wrapper for paraview +with open(outputFile + '.xdmf', 'w') as file: + file.write('\n') + file.write('\n') + file.write('\n') + file.write(' \n') + file.write(' \n') + + # triangles + file.write(' \n' % tris.shape[0]) + file.write(' \n' % tris.shape[0]) + file.write(' ' + outputFile + '.h5:/tris\n') + file.write(' \n') + file.write(' \n') + + # points + file.write(' \n') + file.write(' \n' % xyz.shape[0]) + file.write(' ' + outputFile + '.h5:/verts\n') + file.write(' \n') + file.write(' \n') + + # pattern + file.write(' \n') # node/cell for vertex/triangle attributes + file.write(' \n' % xyz.shape[0]) + file.write(' ' + outputFile + '.h5:/scalar\n') + file.write(' \n') + file.write(' \n') + + file.write(' \n') + file.write(' \n') + file.write('\n') diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..60419d6 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,106 @@ +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # +# Copyright (c) 2019, De Graef Group, Carnegie Mellon University # +# All rights reserved. # +# # +# Author: William C. Lenthe # +# # +# This package is free software; you can redistribute it and/or # +# modify it under the terms of the GNU General Public License as # +# published by the Free Software Foundation; either version 2 of the # +# License, or (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program; if not, check the Free Software Foundation # +# website: # +# # +# # +# Interested in a commercial license? Contact: # +# # +# Center for Technology Transfer and Enterprise Creation # +# 4615 Forbes Avenue, Suite 302 # +# Pittsburgh, PA 15213 # +# # +# phone. : 412.268.7393 # +# email : innovation@cmu.edu # +# website: https://www.cmu.edu/cttec/ # +# # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +################################ +# add executables # +################################ + +# high level tests +add_executable(TestDict ${CMAKE_CURRENT_LIST_DIR}/dict.cpp ) +add_executable(TestDiag ${CMAKE_CURRENT_LIST_DIR}/diagram.cpp ) + +# tests for include/sht/* +add_executable(TestWigner ${CMAKE_CURRENT_LIST_DIR}/sht/wigner.cpp ) +add_executable(TestSquare ${CMAKE_CURRENT_LIST_DIR}/sht/square_sht.cpp ) +add_executable(TestXCorr ${CMAKE_CURRENT_LIST_DIR}/sht/sht_xcorr.cpp ) + +# tests for include/util/* +add_executable(TestBase64 ${CMAKE_CURRENT_LIST_DIR}/util/base64.cpp ) +add_executable(TestLinAlg ${CMAKE_CURRENT_LIST_DIR}/util/linalg.cpp ) +add_executable(TestThread ${CMAKE_CURRENT_LIST_DIR}/util/threadpool.cpp) +add_executable(TestTimer ${CMAKE_CURRENT_LIST_DIR}/util/timer.cpp ) +add_executable(TestColor ${CMAKE_CURRENT_LIST_DIR}/util/colorspace.cpp) +add_executable(TestNML ${CMAKE_CURRENT_LIST_DIR}/util/nml.cpp ) + +# tests for include/xtal/* +add_executable(TestRot ${CMAKE_CURRENT_LIST_DIR}/xtal/rotations.cpp ) +add_executable(TestQuat ${CMAKE_CURRENT_LIST_DIR}/xtal/quaternion.cpp) +add_executable(TestSym ${CMAKE_CURRENT_LIST_DIR}/xtal/symmetry.cpp ) +add_executable(TestPos ${CMAKE_CURRENT_LIST_DIR}/xtal/position.cpp ) +add_executable(TestHM ${CMAKE_CURRENT_LIST_DIR}/xtal/hm.cpp ) + +################################ +# dependencies # +################################ + +if(${EMSPHINX_BUILD_FFTW}) + # high level tests + add_dependencies(TestDict ${FFTW_DEPENDS}) + add_dependencies(TestDiag ${FFTW_DEPENDS}) + + # tests for include/sht/* + add_dependencies(TestSquare ${FFTW_DEPENDS}) + add_dependencies(TestXCorr ${FFTW_DEPENDS}) +endif() +if(${EMSPHINX_BUILD_HDF5}) + # high level tests + add_dependencies(TestDict hdf5) + add_dependencies(TestDiag hdf5) + + # tests for include/sht/* + add_dependencies(TestXCorr hdf5) + + # tests for include/xtal/* + add_dependencies(TestNML hdf5) +endif() + +if(${BuildMiniZ}) + add_dependencies(TestDiag miniz) +endif() + +################################ +# linking # +################################ + +# high level tests +target_link_libraries(TestDict ${FFTW_LIBRARIES} ${HDF5_LIBRARIES}) +target_link_libraries(TestDiag ${FFTW_LIBRARIES} ${HDF5_LIBRARIES}) + +# include/sht/* +target_link_libraries(TestSquare ${FFTW_LIBRARIES}) +target_link_libraries(TestXCorr ${FFTW_LIBRARIES} ${HDF5_LIBRARIES}) + +# include/util/* +target_link_libraries(TestNML ${HDF5_LIBRARIES}) + diff --git a/test/diagram.cpp b/test/diagram.cpp new file mode 100644 index 0000000..e7ca0eb --- /dev/null +++ b/test/diagram.cpp @@ -0,0 +1,101 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "xtal/diagram.hpp" + +int main(int argc, char *argv[]) { + if(2 == argc) {//make a diagram from a master pattern file + std::string fileName(argv[1]); + emsphinx::MasterPattern mp(fileName);//read master pattern + svg::Color c(0, 0, 0); + xtal::Diagram diag(mp, c); + diag.getHemi(true ).write("north.svg"); + diag.getHemi(false).write("south.svg"); + } else {//make plain diagrams + //enumerate point group names to construct + std::vector groups = { + "1", + "-1", + "121",//multiple settings + "112",//multiple settings + "1m1",//multiple settings + "11m",//multiple settings + "12/m1",//multiple settings + "112/m",//multiple settings + "222", + "mm2", + "mmm", + "4", + "-4", + "4/m", + "422", + "4mm", + "-42m",// multiple settings + "-4m2",// multiple settings + "4/mmm", + "3", + "-3", + "321",// multiple settings + "312",// multiple settings + "3m1",// multiple settings + "31m",// multiple settings + "-3m1",// multiple settings + "-31m",// multiple settings + "6", + "-6", + "6/m", + "622", + "6mm", + "-6m2",// multiple settings + "-62m",// multiple settings + "6/mmm", + "23", + "m3", + "432", + "-43m", + "m3m" + }; + + //loop over point groups building diagram + for(std::string& pg : groups) { + std::string name = pg; + replace(name.begin(), name.end(), '/', '_');//dont allow slashes in file name + xtal::Diagram dg(pg); + dg.addLabel(pg); + dg.getHemi().write(name + ".svg"); + } + } + + return 0; +} diff --git a/test/dict.cpp b/test/dict.cpp new file mode 100644 index 0000000..b3fa204 --- /dev/null +++ b/test/dict.cpp @@ -0,0 +1,345 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + +#include + +#include "H5Cpp.h" +#include "modality/ebsd/detector.hpp" + +struct Dictionary { + std::vector eu ;//euler angles + std::vector pat;//patterns + size_t pix;//pixels per ebsd pattern + size_t num;//number of orientations in dictionary + emsphinx::ebsd::Geometry geo;//geometry + + //@brief : get a pointer to the start of an euler agnle + //@param i: euler angle number to get pointer to + //@return : pointer to euler angle start + float const * getEu (const size_t i) const {return eu .data() + i * 3 ;} + + //@brief : get a pointer to the start of a pattern + //@param i: pattern number to get pointer to + //@return : pointer to pattern start + float const * getPat(const size_t i) const {return pat.data() + i * pix;} + + //@brief : get the number of patterns in the dictionary + //@return : total number of patterns + size_t numPat() const {return num;} + + //@brief : read a dictionary + //@param fileName: name of dictionary file to read + //@param maxPat : maximum number of patterns to read (0 for all) + Dictionary(std::string fileName, const size_t maxPat = 0); + + //@brief : get detector geometry + //@param tlt: sample tilt + //@return : geometry + template emsphinx::ebsd::Geometry getGeom(Real tlt) const; +}; + +#include + +//@brief : write a namelist template file +//@param os: ostream to write namelist template to +//@return : os +std::ostream& writeDictNml(std::ostream& os) { + os << " &Placeholder\n"; + os << "!#################################################################\n"; + os << "! Indexing Parameters\n"; + os << "!#################################################################\n"; + os << "\n"; + os << "! spherical harmonic bandwidth to be used (2*bw-1 should be a product of small primes for speed)\n"; + os << "! some reasonable values are: 53, 63, 68, 74, 88, 95, 113, 123, 158\n"; + os << " bw = 68,\n"; + os << "\n"; + os << "! should newton's method based orientation refinement be used?\n"; + os << " refine = .TRUE.,\n"; + os << "\n"; + os << "! number of work threads\n"; + os << "! 0 to multithread with an automatic number of threads\n"; + os << "! 1 for serial threading\n"; + os << "! N to multithread with N threads\n"; + os << " nthread = 0,\n"; + os << "\n"; + os << "! number of patterns to index per work itme (ignored for single threading)\n"; + os << "! should be large enough to make the task significant compared to thread overhead\n"; + os << "! should be small enough to enable enough work items for load balancing\n"; + os << "! 0 to estimate a reasonable value based on speed\n"; + os << " batchsize = 0,\n"; + os << "\n"; + os << "\n"; + os << "!#################################################################\n"; + os << "! Camera Calibration\n"; + os << "!#################################################################\n"; + os << "\n"; + os << "! sample tilt angle from horizontal [degrees]\n"; + os << " sig = 70.0,\n"; + os << "\n"; + os << "\n"; + os << "!#################################################################\n"; + os << "! Input Data\n"; + os << "!#################################################################\n"; + os << "\n"; + os << "! input path, empty for current working directory\n"; + os << " ipath = '',\n"; + os << "\n"; + os << "! master pattern with phases to index (relative to ipath)\n"; + os << " masterfile = 'master.h5',\n"; + os << "\n"; + os << "! dictionary file to index (relative to ipath)\n"; + os << " dictfile = 'dict.h5',\n"; + os << "\n"; + os << "! number of patterns to consider (0 for all)\n"; + os << " numpat = 0\n"; + os << " /\n"; + return os; +} + +//@brief : write program usage instructions +//@param os: ostream to write instructions +//@return : os +std::ostream& writeInstructions(std::ostream& os, char* name) { + os << "useage: "; + os << "\tindex using a nml file : " << name << "input.nml\n"; + os << "\tgenerate a template nml: " << name << "-t\n"; + return os; +} + +#include + +#include "modality/ebsd/idx.hpp" +#include "xtal/rotations.hpp" +#include "util/threadpool.hpp" +#include "util/timer.hpp" +#include "util/nml.hpp" +#include "idx/indexer.hpp" + +int main(int argc, char *argv[]) { + //////////////////////////////////////////////////////////////////////// + // Parse Arguments // + //////////////////////////////////////////////////////////////////////// + + //check argument count + if(2 != argc) { + writeInstructions(std::cout, argv[0]); + return EXIT_FAILURE; + } + + //check for template request + const std::string nmlName(argv[1]); + if(0 == nmlName.compare("-t")) { + std::ofstream os(std::string(argv[0]) + ".nml");//create programname.nml + writeDictNml(os);//write template to nml file + return EXIT_SUCCESS; + } + + //read nml and parse + nml::NameList nameList(nmlName); + typedef double Real;//should we use float, double, or long double for calculations? + + //parse indexing parameters + const size_t bw = (size_t) nameList.getInt ("bw" );//what bandwidth should be used, if 2*bw-1 is product of small primes it is a good candidate for speed (fft is significant fraction of time): 32,38,41,53,63,68,74,88,95,113,123,158 + const bool refine = (size_t) nameList.getBool("refine" );//should newton refinement be used + const size_t nThread = (size_t) nameList.getInt ("nthread" ); + size_t batchSize = (size_t) nameList.getInt ("batchsize");//number of patterns per work item (should be large enough that the task is significant but small enough that there are enough jobs for load balancing) + + //parse geometry + const double sampleTilt = nameList.getDouble("sig"); + + //parse input files + std::string ipath, scanName, patName; + try { ipath = nameList.getString("ipath" );} catch (...) {}//if ipath isn't found we'll just use cwd + std::string masterFile = ipath + nameList.getString("masterfile"); + std::string dictFile = ipath + nameList.getString("dictfile" ); + const size_t maxPat = nameList.getInt ("numpat" ); + + //check for unused inputs + if(!nameList.fullyParsed()) std::cout << "\nwarning - some namelist parameters weren't used: " << nameList.unusedTokens() << "\n" << std::endl; + + //////////////////////////////////////////////////////////////////////// + // Read Inputs // + //////////////////////////////////////////////////////////////////////// + + //read master pattern, get symmetry, and read dictionary + std::vector< emsphinx::MasterSpectra > phases; + emsphinx::MasterSpectra spec(emsphinx::MasterPattern(masterFile), (uint16_t)bw);//compute SHT of master patterns once + xtal::PointGroup sym(spec.pointGroup()); + Dictionary dict(dictFile, maxPat); + + //build image processor + emsphinx::ebsd::Geometry geom = dict.getGeom(sampleTilt); + std::unique_ptr< emsphinx::ebsd::PatternProcessor > prc(new emsphinx::ebsd::PatternProcessor()); + prc->setSize(geom.w, geom.h, -1, false, 0);//no circular mask, no background subtraction, no AHE + + //build back projector + const size_t gridDim = bw + (bw % 2 == 0 ? 3 : 2); + std::array quNp = geom.northPoleQuat(); + std::unique_ptr< emsphinx::ebsd::BackProjector > prj(new emsphinx::ebsd::BackProjector(geom, gridDim, std::sqrt(Real(2)), quNp.data())); + + //build unnormalized spherical cross correlators + std::vector< std::unique_ptr< emsphinx::sphere::PhaseCorrelator > > corrs; + std::shared_ptr< std::vector< std::complex > > flm = std::make_shared< std::vector< std::complex > >(spec.data(), spec.data() + bw * bw);//copy harmonics into shared pointer + std::unique_ptr< emsphinx::sphere::UnNormalizedCorrelator > pCorr(new emsphinx::sphere::UnNormalizedCorrelator((int)bw, flm, spec.mirror(), spec.nFold() ) );//build correlator + corrs.push_back( std::move( pCorr ) ); + + //determine threading parameters and build indexers + const size_t threadCount = nThread == 0 ? ThreadPool::Concurrency() : nThread; + if(0 == batchSize) batchSize = emsphinx::Indexer::BatchEstimate(bw, threadCount, dict.numPat()); + ThreadPool pool(threadCount);//pool + + //build a single indexer for each thread + std::unique_ptr > idx(new emsphinx::Indexer((int)bw, prc->clone(), prj->clone(), corrs));//make a single indexer (good for 1 thread) + std::vector< std::shared_ptr< emsphinx::Indexer > > indexers; + indexers.push_back(std::move(idx));//move original indexer onto + for(size_t i = 1; i < threadCount; i++) indexers.push_back( std::move(indexers.front()->clone()) );//duplicate n-1 times + + //allocate space to hold indexing result and build work item + std::vector exceptStr(dict.numPat()); + std::vector< emsphinx::Result > res(dict.numPat());//allocate space for results + std::function workItem = [&](const size_t start, const size_t end, const size_t idx){//work function + for(size_t i = start; i < end; i++) { + try { + indexers[idx]->indexImage(dict.getPat(i), &res[i], 1, refine);//index corresponding pattern + } catch (std::exception& e) { + exceptStr[i] = e.what(); + } + } + }; + + //////////////////////////////////////////////////////////////////////// + // Do Indexing // + //////////////////////////////////////////////////////////////////////// + + //parallel index + Timer t; + size_t batches = dict.numPat() / batchSize;//how many batches are needed + if(batches * batchSize < dict.numPat()) ++batches;//extra batch for leftovers + for(size_t i = 0; i < batches; i++) {//loop over batches + const size_t start = i * batchSize;//first pattern + const size_t end = std::min(start + batchSize, dict.numPat());//last pattern + pool.schedule(std::bind(workItem, start, end, std::placeholders::_1));//queue indexing + } + pool.waitAll();//wait for work to finish + + //print results (+serial index if needed) + Real eu[3], qu[4], diso[4]; + for(size_t i = 0; i < dict.numPat(); i++) { + Real * qr = res[i].qu; + // for(size_t j = 1; j < 4; j++) qr[j] = -qr[j];////////////////////////////!!!!!!TEMPROARY TRY NEGATE + std::copy(dict.getEu(i), dict.getEu(i) + 3, eu);//get answer + xtal::eu2qu(eu, qu);//convert to quaternion + if(0 == res[i].phase) {//result found + sym.disoQu(qu, qr, diso);//compute disorientation + Real delta = std::acos(diso[0]) * 360.0 / emsphinx::Constants::pi;//get disorientation angle + std::cout << '\t' << i << ": " << delta << '\n';//print error + if(delta > 2) {//print full result for debugging in case of large error + std::cout << "\t\t" << qu [0] << ' ' << qu [1] << ' ' << qu [2] << ' ' << qu [3] << '\n'; + std::cout << "\t\t" << qr [0] << ' ' << qr [1] << ' ' << qr [2] << ' ' << qr [3] << '\n'; + std::cout << "\t\t" << diso[0] << ' ' << diso[1] << ' ' << diso[2] << ' ' << diso[3] << '\n'; + } + } else {//error during refinement + std::cout << '\t' << i << ": " << exceptStr[i] << '\n'; + } + } + + std::cout << t.poll() << '\n'; + return 0; +} + +//////////////////////////////////////////////////////////////////////// +// Dictionary Details // +//////////////////////////////////////////////////////////////////////// + +//@brief : read a dictionary +//@param fileName: name of dictionary file to read +Dictionary::Dictionary(std::string fileName, const size_t maxPat) { + //first open the h5 file and read size + hsize_t dims[3];//2 or 3 if makeditctionary was true/false + H5::H5File file = H5::H5File(fileName.c_str(), H5F_ACC_RDONLY);//read only access + H5::DataSet eulers = file.openDataSet("EMData/EBSD/EulerAngles"); + H5::DataSet patterns = file.openDataSet("EMData/EBSD/EBSDPatterns"); + patterns.getSpace().getSimpleExtentDims(dims);//read extent in each dimension + num = maxPat == 0 ? dims[0] : std::min(dims[0], maxPat); + const size_t nDims = patterns.getSpace().getSimpleExtentNdims(); + const bool vectorized = nDims == 2; + if(!vectorized) { + if(nDims != 3) throw std::runtime_error("only 2d or 3d patterns are supported"); + } + pix = vectorized ? dims[1] : dims[1] * dims[2]; + + //select hyperslabs to read only first num patterns/euler angles + hsize_t slabOffsets[3];// hyperslab offset in memory + hsize_t euSlabDims[2]; // size of the hyperslab in memory + hsize_t patSlabDims[3]; // size of the hyperslab in memory + slabOffsets[0] = slabOffsets[1] = slabOffsets[2] = 0; + euSlabDims[0] = num; + euSlabDims[1] = 3; + patSlabDims[0] = num; + patSlabDims[1] = pix; + if(!vectorized) { + patSlabDims[1] = dims[1]; + patSlabDims[2] = dims[2]; + } + H5::DataSpace euSpace = eulers .getSpace(); + H5::DataSpace patSpace = patterns.getSpace(); + + euSpace.selectHyperslab(H5S_SELECT_SET, euSlabDims, slabOffsets); + patSpace.selectHyperslab(H5S_SELECT_SET, patSlabDims, slabOffsets); + + //allocate memory and read + eu .resize(3 * num); + pat.resize(pix * num); + eulers .read(eu .data(), H5::PredType::NATIVE_FLOAT, euSpace, euSpace); + patterns.read(pat.data(), H5::PredType::NATIVE_FLOAT, patSpace, patSpace); + + //also parse geometery + geo.readEMsoft(file.openGroup("NMLparameters/EBSDNameList")); +} + +//@brief : get detector geometry +//@param tlt: sample tilt +//@return : geometry +template +emsphinx::ebsd::Geometry Dictionary::getGeom(Real tlt) const { + emsphinx::ebsd::Geometry geom; + geom.sampleTilt (tlt );//sample tilt in degrees + geom.cameraTilt (geo.dTlt );//camera tilt in degrees + geom.cameraSize (geo.w , geo.h , geo.pX );///width, height, pixel size in microns + geom.patternCenter(geo.cX , geo.cY, geo.sDst);//x/y pattern center + scintilator distance + geom.maskPattern (geo.circ ); + geom.flipPattern (geo.flip ); + return geom; +} diff --git a/test/sht/sht_xcorr.cpp b/test/sht/sht_xcorr.cpp new file mode 100644 index 0000000..ca1f52e --- /dev/null +++ b/test/sht/sht_xcorr.cpp @@ -0,0 +1,398 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +//////////////////////////////////////////////////////////////////////// +// test program for classes in include/sht/sht_xcorr.hpp // +//////////////////////////////////////////////////////////////////////// + +//!!!!!!!! +// Tests are still missing for normalized cross correlation +//!!!!!!!! + +#include + +namespace emsphinx { + + namespace sphere { + //@brief : test spherical cross correlation + //@param bw : bandwidth to test + //@param mir : should a z mirror plane be used + //@param nFld: degree of rotational symmtry about z axis + //@param qu : optional location to write applied rotation + //@param qr : optional location to write registered rotation + //@return : angle between applied and registered rotation + //@note : only tests unnormalized cross correlation + template Real testCorr(const size_t bw, const bool mir, const size_t nFld, Real* qu = NULL, Real* qr = NULL); + + //@brief : test normalized spherical cross correlation + //@param bw : bandwidth to test + //@param mir : should a z mirror plane be used + //@param nFld: degree of rotational symmtry about z axis + //@param qu : optional location to write applied rotation + //@param qr : optional location to write registered rotation + //@return : angle between applied and registered rotation + //@note : only tests unnormalized cross correlation + template Real testNCorr(const size_t bw, const bool mir, const size_t nFld, Real* qu = NULL, Real* qr = NULL); + + //@brief : run all spherical cross correlation test for a range of parameters + //@param os: location to write errors + //@return : true / false if the tests pass/fail + template bool runTests(std::ostream& os); + } + +} + + +int main() { + try { + return emsphinx::sphere::runTests(std::cout) ? EXIT_SUCCESS : EXIT_FAILURE; + } catch(std::exception& e) { + std::cout << "caught: " << e.what() << '\n'; + return EXIT_FAILURE; + } +} + +#include +#include + +#include "sht/wigner.hpp" +#include "sht/square_sht.hpp" +#include "sht/sht_xcorr.hpp" + +#include "xtal/rotations.hpp" +#include "xtal/quaternion.hpp" +#include "idx/master.hpp" + +namespace emsphinx { + + namespace sphere { + + //@brief : generate a random spherical function + //@param gen : random generator + //@param dim : side length of spherical grid + //@param mir : should the function have a mirror plane at the equator + //@param nFld: order of rotational symmetry about z axis + //@return : random spherical function with specified size and symmetry + template + emsphinx::MasterPattern randomSphere(std::mt19937_64& gen, const size_t dim, const bool mir, const size_t nFld) { + //start with a random north hemisphere + std::uniform_real_distribution dist(-1,1);//build a random distribution + emsphinx::MasterPattern mp(dim);//create an empty master pattern + mp.lyt = square::Layout::Legendre; + for(Real& v : mp.nh) v = dist(gen);//fill + + //apply mirror symmetry if needed + if(mir) { + mp.sh = mp.nh;//z mirror + } else { + for(Real& v : mp.sh) v = dist(gen);//build random south hemisphere + mp.matchEquator();//make sure equators are the same + } + + //apply rotational symmetry if needed + if(nFld > 1) mp.makeNFold(nFld); + + return mp; + } + + //@brief : generate a random rotation + //@param gen : random generator + //@return : random rotation + template + xtal::Quat randomRotation(std::mt19937_64& gen) { + std::uniform_real_distribution dist(-1,1);//build a random distribution + return xtal::Quat(std::fabs(dist(gen)), dist(gen), dist(gen), dist(gen)).normalize(); + } + + //@brief : generate the spectra of a random pattern on the sphere and rotate it + //@param bw : bandwidth of spectra to generate + //@param mir : should the spectra correspond to a function with z mirror symmetry + //@param nFld: order of rotational symmetry about z axis in reference pattern + //@param flm : location to write spectra of original unrotated (reference) function + //@param gln : location to write spectra of rotated function + //@return : rotation applied to gln as quaternion + template + xtal::Quat randomPair(const size_t bw, const bool mir, const size_t nFld, std::vector< std::complex >& flm, std::vector< std::complex >& gln) { + //build a random generator + const unsigned int seed = 0;//constant for deterministic behavior or time(NULL) for random + std::mt19937_64 gen(seed);//64 bit mersenne twister + + //generate a random function with the desired symmetry + const size_t dim = bw + (bw % 2 == 0 ? 3 : 2); + emsphinx::MasterPattern mp = randomSphere(gen, dim, mir, nFld); + + //compute the SHT of the master pattern + emsphinx::MasterSpectra spec(mp, (uint16_t)bw, false); + flm = std::vector< std::complex >(spec.data(), spec.data() + bw * bw); + + //exactly enforce rotational symmetry (since the real space implementation is a bit kludgey for # ring pts % nFld != 0) + if(nFld > 1) { + for(size_t m = 0; m < bw; m++) { + if(0 != m % nFld) { + std::fill(flm.begin() + m * bw, flm.begin() + (m+1) * bw, std::complex(0)); + } + } + } + + //apply a random rotation + Real eu[3]; + xtal::Quat qu = randomRotation(gen); + xtal::qu2zyz(qu.data(), eu);//convert to zyz euler angles + gln.resize(flm.size()); + wigner::rotateHarmonics(bw, flm.data(), gln.data(), eu); + return qu; + } + + //@brief : test spherical cross correlation + //@param bw : bandwidth to test + //@param mir : should a z mirror plane be used + //@param nFld: degree of rotational symmetry about z axis + //@param qu : optional location to write applied rotation + //@param qr : optional location to write registered rotation + //@return : angle between applied and registered rotation + template Real testCorr(const size_t bw, const bool mir, const size_t nFld, Real* qu, Real* qr) { + //generate a pair of rotated harmonics + std::vector< std::complex > flm, gln; + xtal::Quat u = randomPair(bw, mir, nFld, flm, gln); + + //compute the rotation of the peak cross correlation + Real eu[3], r[4]; + sphere::Correlator s2(bw);//spherical cross correlation calculator + Real xc = s2.correlate(flm.data(), gln.data(), mir, nFld, eu, true); + xtal::zyz2qu(eu, r);//convert to a quaternion + + //save rotations if needed + //these are conjugated to convert from crystal->sample to sample->crystal so that symmetry operators are correct + if(NULL != qu) xtal::quat::conj(u.data(), qu); + if(NULL != qr) xtal::quat::conj(r , qr); + + //compare registered with applied rotations + const Real dot = std::inner_product(r, r + 4, u.data(), Real(0)); + return 180.0 * std::acos(std::min(dot, Real(1))) / emsphinx::Constants::pi; + } + + //@brief : test spherical cross correlation + //@param bw : bandwidth to test + //@param mir : should a z mirror plane be used + //@param nFld: degree of rotational symmetry about z axis + //@param qu : optional location to write applied rotation + //@param qr : optional location to write registered rotation + //@return : angle between applied and registered rotation + template Real testNCorr(const size_t bw, const bool mir, const size_t nFld, Real* qu, Real* qr) { + //build a random generator + const unsigned int seed = 0;//constant for deterministic behavior or time(NULL) for random + std::mt19937_64 gen(seed);//64 bit mersenne twister + + //build a reasonable mask (something with minimal symmetry that has points in both hemispheres) + const size_t dim = bw + (bw % 2 == 0 ? 3 : 2); + std::vector mask(dim * dim * 2, 0), norms(dim * dim * 3); + + //get normal directions for each square legendre grid point and build mask limits + square::legendre::normals(dim, norms.data()); + const Real tMin = (Real) -0.52359877559829887307710723054658L;// -30 degrees + const Real tMax = (Real) 1.04719755119659774615421446109320L;// 60 degrees + const Real zMax = (Real) 0.70710678118654752440084436210485L;// maximum negative z (45 degrees) + + //set mask points + for(size_t j = 0; j < dim; j++) { + for(size_t i = 0; i < dim; i++) { + const size_t idx = j * dim + i; + Real * n = norms.data() + 3 * idx;//get normal for this point (in NH) + const Real theta = std::atan2(n[1], n[0]);//get angle + if(theta >= tMin && theta <= tMax) {//inside of wedge + mask[idx] = Real(1);//set north hemisphere for any lattitude + if(n[2] < zMax) mask[idx+dim*dim] = Real(1); + } + } + } + + //build a SHT calculator + square::DiscreteSHT sht(dim, bw, square::Layout::Legendre); + + //generate a random function and do a round trip to remove frequencies above bandwidth + std::vector< std::complex > flm(bw * bw), flm2(bw * bw), gln(bw * bw), mlm(bw * bw); + emsphinx::MasterPattern mp = randomSphere(gen, dim, mir, nFld); + sht.analyze(mp.nh.data(), mp.sh.data(), flm.data()); + sht.synthesize(flm.data(), mp.nh.data(), mp.sh.data()); + + //now compute SHT of function squared + for(Real& r : mp.nh) r *= r; + for(Real& r : mp.sh) r *= r; + sht.analyze(mp.nh.data(), mp.sh.data(), flm2.data()); + + //next do the same for the mask + sht.analyze(mask.data(), mlm.data()); + sht.synthesize(mlm.data(), mask.data()); + + //now apply a random rotation to the original function + Real eu[3]; + xtal::Quat u = randomRotation(gen); + xtal::qu2zyz(u.data(), eu);//convert to zyz euler angles + wigner::rotateHarmonics(bw, flm.data(), gln.data(), eu); + + //mask the rotated function + std::vector work(dim * dim * 2); + sht.synthesize(gln.data(), work.data()); + std::transform(work.cbegin(), work.cend(), mask.cbegin(), work.begin(), std::multiplies()); + sht.analyze(work.data(), gln.data()); + + //build normalized spherical cross correlation calculator + sphere::NormalizedCorrelator s2(bw, flm.data(), flm2.data(), mir, nFld, mlm.data()); + + //compute the rotation of the peak normalized cross correlation + Real r[4]; + Real xc = s2.correlate(gln.data(), eu, true); + xtal::zyz2qu(eu, r);//convert to a quaternion + + //save rotations if needed + //these are conjugated to convert from crystal->sample to sample->crystal so that symmetry operators are correct + if(NULL != qu) xtal::quat::conj(u.data(), qu); + if(NULL != qr) xtal::quat::conj(r , qr); + + //compare registered with applied rotations + const Real dot = std::inner_product(r, r + 4, u.data(), Real(0)); + // return 100; + return 180.0 * std::acos(std::min(dot, Real(1))) / emsphinx::Constants::pi; + } + + //@brief : run all spherical cross correlation test for a range of parameters + //@param os: location to write errors + //@return : true / false if the tests pass/fail + template bool runTests(std::ostream& os) { + //build tolerance and sizes + Real eps = std::cbrt(std::numeric_limits::epsilon());//this is ~6e-6 degrees for double and 0.005 degrees for float + const std::vector sizes = { + 53, 68, 88, 113, 123, 158, //some fast sizes + 54, 55, 56, 57 , 58 , 59 , 60, 62, 64,//some sizes that need to be 0 padded for speed + }; + + //now loop over sizes doing testing + xtal::Quat qu, qr; + os << "testing symmetry free cross correlation" << std::endl; + for(const size_t& i : sizes) { + os << '\t' << i << '\n'; + const Real delta = testCorr(i, false, 1, qu.data(), qr.data()); + if(delta > eps) { + os << "registered rotation is " << delta << " degrees away from applied\n"; + os << "\tbandwidth : " << i << '\n'; + os << "\tapplied : " << qu << '\n'; + os << "\tregistered: " << qr << '\n'; + return false; + } + } + + //now loop over sizes doing normalized testing + Real epsN = eps * 10;//allow additional error introduced by the masking + os << "testing symmetry free normalized cross correlation" << std::endl; + for(const size_t& i : sizes) { + os << '\t' << i << '\n'; + const Real delta = testNCorr(i, false, 1, qu.data(), qr.data()); + if(delta > epsN) { + os << "registered rotation is " << delta << " degrees away from applied\n"; + os << "\tbandwidth : " << i << '\n'; + os << "\tapplied : " << qu << '\n'; + os << "\tregistered: " << qr << '\n'; + os << epsN << '\n'; + return false; + } + } + + //build combination of expected z mirror and nFold from basic crystallographic point groups + std::vector pgs = { + // mir nFld + xtal::PointGroup("112" ),// F 2 + xtal::PointGroup("11m" ),// T 1 + xtal::PointGroup("112/m"),// T 2 + xtal::PointGroup("3" ),// F 3 + xtal::PointGroup("4" ),// F 4 + xtal::PointGroup("4/m" ),// T 4 + xtal::PointGroup("6" ),// F 6 + xtal::PointGroup("6/m" ),// T 6 + }; + + //loosen tolerance some to accommodate symmetry + eps = std::sqrt(eps) * 5;//this is ~0.012 and 0.35 degrees for double and float respectively + + //test each for a range of bandwidths + for(const xtal::PointGroup& pg : pgs) {//loop over point groups + os << "testing cross correlation for point group " << pg.name() << std::endl; + for(size_t i = 53; i < 64; i++) {//smaller sample of faster sizes since there are a lot of tests in this loop + Real delta = testCorr(i, pg.zMirror(), pg.zRot(), qu.data(), qr.data());//no symmetry accounted for + if(delta > eps) {//make sure we don't have a symmetry operator + xtal::Quat qw; + pg.disoQu(qu.data(), qr.data(), qw.data()); + const Real deltaSym = 180.0 * std::acos(std::min(qw.w, Real(1))) / emsphinx::Constants::pi; + delta = std::min(delta, deltaSym); + } + + if(delta > eps) { + os << "Registered rotation is " << delta << " degrees away from applied\n"; + os << "\tbandwidth : " << i << '\n'; + os << "\tapplied : " << qu << '\n'; + os << "\tregistered : " << qr << '\n'; + os << "\tpoint group: " << pg.name() << '\n'; + return false; + } + } + } + + //normalized test each for a range of bandwidths + for(const xtal::PointGroup& pg : pgs) {//loop over point groups + os << "testing normalized cross correlation for point group " << pg.name() << std::endl; + for(size_t i = 53; i < 64; i++) {//smaller sample of faster sizes since there are a lot of tests in this loop + Real delta = testNCorr(i, pg.zMirror(), pg.zRot(), qu.data(), qr.data());//no symmetry accounted for + if(delta > eps) {//make sure we don't have a symmetry operator + xtal::Quat qw; + pg.disoQu(qu.data(), qr.data(), qw.data()); + const Real deltaSym = 180.0 * std::acos(std::min(qw.w, Real(1))) / emsphinx::Constants::pi; + delta = std::min(delta, deltaSym); + } + + if(delta > eps) { + os << "Registered rotation is " << delta << " degrees away from applied\n"; + os << "\tbandwidth : " << i << '\n'; + os << "\tapplied : " << qu << '\n'; + os << "\tregistered : " << qr << '\n'; + os << "\tpoint group: " << pg.name() << '\n'; + return false; + } + } + } + + //if we made it this far everything passed + return true; + } + } + +} diff --git a/test/sht/square_sht.cpp b/test/sht/square_sht.cpp new file mode 100644 index 0000000..e3421ca --- /dev/null +++ b/test/sht/square_sht.cpp @@ -0,0 +1,205 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +//////////////////////////////////////////////////////////////////////// +// test program for functions/classes in include/sht/square_sht.hpp // +//////////////////////////////////////////////////////////////////////// + +#include +#include "sht/square_sht.hpp" + +namespace emsphinx { + + namespace square { + //@brief : test DiscreteSHT for self consistent behavior for a specific condition + //@param bw : bandwidth to test + //@param lyt : layout of square grid + //@param maxEps: maximum allowable absolute error for a single harmonic coefficient + //@param avgEps: maximum average error for a harmonic coefficient + //@param os : location to write error messages + //@return : true/false if test passes/fails + template bool testSingleSHT(const size_t bw, Layout lyt, const Real maxEps, const Real avgEps, std::ostream& os); + + //@brief : test DiscreteSHT for self consistent behavior over a range of reasonable conditions + //@param os: location to write error messages + //@return : true/false if test passes/fails + bool testSHT(std::ostream& os); + } + +} + +int main() { + //select location to write output + std::ostream& os = std::cout; + + //do tests + try { + return emsphinx::square::testSHT(os) ? EXIT_SUCCESS : EXIT_FAILURE; + } catch(std::exception& e) { + os << "caught: " << e.what() << '\n'; + return EXIT_FAILURE; + } +} + +#include +#include +#include +#include + +namespace emsphinx { + + namespace square { + //@brief : test DiscreteSHT for self consistent behavior + //@param bw : bandwidth to test + //@param lyt : layout of square grid + //@param maxEps: maximum allowable absolute error for a single harmonic coefficient + //@param avgEps: maximum average error for a harmonic coefficient + //@param os : location to write error messages + //@return : true/false if test passes/fails + template bool testSingleSHT(const size_t bw, Layout lyt, const Real maxEps, const Real avgEps, std::ostream& os) { + try { + //build a random distribution + const unsigned int seed = 0;//constant for deterministic behavior or time(NULL) for random + std::mt19937_64 gen(seed);//64 bit mersenne twister + std::uniform_real_distribution dist(-1,1); + + //generate a random spectra + std::vector< std::complex > refSpec(bw * bw, std::complex(0)); + std::vector< std::complex > newSpec(bw * bw, std::complex(0)); + for(size_t m = 0; m < bw; m++) { + for(size_t l = m; l < bw; l++) { + if(m == 0) + refSpec[m*bw+l] = std::complex(dist(gen), 0 ); + else + refSpec[m*bw+l] = std::complex(dist(gen), dist(gen)); + } + } + + //build discrete SHT calculators + const size_t dimLam = 2 * bw + 1; + const size_t dimLeg = bw + (bw % 2 == 0 ? 3 : 2);//only odd side lengths are supported + const size_t dim = lyt == Layout::Legendre ? dimLeg : dimLam; + DiscreteSHT sht(dim, bw, lyt); + + //do a round trip calculation + std::vector func(dim * dim * 2); + sht.synthesize(refSpec.data(), func.data()); + sht.analyze(func.data(), newSpec.data()); + + //compare computed spectra + Real avgErr = 0; + for(size_t m = 0; m < bw; m++) { + for(size_t l = 0; l < bw; l++) { + std::complex delta = newSpec[m*bw+l] - refSpec[m*bw+l]; + Real rDelta = std::max(std::fabs(delta.real()), std::fabs(delta.imag())); + if(rDelta > maxEps) { + os << std::setprecision(std::numeric_limits::digits10); + os << "original : " << refSpec[m*bw+l] << '\n'; + os << "round trip: " << newSpec[m*bw+l] << '\n'; + os << "delta (%) : " << rDelta << '\n'; + os << "limit : " << maxEps << '\n'; + os.flush(); + return false; + } + avgErr += rDelta; + } + } + avgErr /= bw * bw; + if(avgErr > avgEps) { + os << "average error (" << avgEps << ") exceeds limit (" << avgEps << ")" << std::endl; + return false; + } + } catch (std::exception& e) { + os << e.what() << std::endl; + return false; + } + return true; + } + + + //@brief : test DiscreteSHT for self consistent behavior over a range of reasonable conditions + //@param os: location to write error messages + //@return : true/false if test passes/fails + bool testSHT(std::ostream& os) { + //test double precision transform + const double dMax = 0.005 ;//max absolute error + const double dAvg = 0.00005;//max average error + os << "testing round trip double transforms\n"; + os << "\tlegendre grid\n"; + for(size_t i = 4; i <= 384; i++) { + if(0 == i % 8) os << '\t' << i << '\n'; + if(!square::testSingleSHT(i, square::Layout::Legendre, dMax, dAvg, os)) { + os << "bw == " << i << '\n'; + return false; + } + } + os << "\tlambert grid\n"; + for(size_t i = 4; i <= 128; i++) { + if(0 == i % 8) os << '\t' << i << '\n'; + if(!square::testSingleSHT(i, square::Layout::Lambert , dMax, dAvg, os)) { + os << "bw == " << i << '\n'; + return false; + } + } + + #ifdef EM_USE_F + //test single precision transform + const float fMax = 0.020 ;//max absolute error + const float fAvg = 0.00020;//max average error + os << "testing round trip float transforms\n"; + os << "\tlegendre grid\n"; + for(size_t i = 4; i <= 192; i++) { + if(0 == i % 8) os << '\t' << i << '\n'; + if(!square::testSingleSHT(i, square::Layout::Legendre, fMax, fAvg, os)) { + os << "bw == " << i << '\n'; + return false; + } + } + os << "\tlambert grid\n"; + for(size_t i = 4; i <= 64; i++) { + if(0 == i % 8) os << '\t' << i << '\n'; + if(!square::testSingleSHT(i, square::Layout::Lambert , fMax, fAvg, os)) { + os << "bw == " << i << '\n'; + return false; + } + } + #endif + + //if we made it this far everything passed + return true; + } + } + +} + diff --git a/test/sht/wigner.cpp b/test/sht/wigner.cpp new file mode 100644 index 0000000..727d6a6 --- /dev/null +++ b/test/sht/wigner.cpp @@ -0,0 +1,880 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + +//////////////////////////////////////////////////////////////////////// +// test program for functions in include/sht/wigner.hpp // +//////////////////////////////////////////////////////////////////////// + +#include + +namespace emsphinx { + + namespace wigner { + //@brief : test d^j_{k,m}(\beta) functions + //@param os: output stream to write error messages to + //@return : true/false if the functions are consistent with precomputed values from mathematica's WignerD[{j,k,m},\beta] + //@note : functions tested + // - Real d(const int64_t j, const int64_t k, const int64_t m, const Real t, const bool nB) + // - Real d(const int j, const int k, const int m) + // - int dSign(const int j, const int k, const int m) + // -std::complex D(const int64_t j, const int64_t k, const int64_t m, Real const * const eu) + template bool testDjkm(std::ostream& os); + + //@brief : test all the table functions + //@param j : max degree in (d/dBeta)^2 d^j_{k,m}(beta) + //@param os: output stream to write error messages to + //@return : true/false if the table functions are consistent with the single point recursive functions + //@note : functions tested + // -void dTable(const size_t jMax, const Real t, const bool nB, Real * const table) + // -void dTablePre(const size_t jMax, const Real t, const bool nB, Real * const table, Real const * const pE, Real const * const pW, Real const * const pB) + // -void dTablePreBuild(const size_t jMax, Real * const pE, Real * const pW, Real * const pB) + // -void dTable(const size_t jMax, Real * const table, const bool trans = false) + template bool testTables(const int64_t bw, std::ostream& os); + + //@brief : test functions for derivatives of d^j_{k,m}(\beta) + //@param os: output stream to write error messages to + //@return : true/false if the functions are consistent with precomputed values from mathematica's D[WignerD[{j,k,m},\beta],{\beta, (1 or 2)}] + //@note : functions tested + // -Real dPrime(const int64_t j, const int64_t k, const int64_t m, const Real t, const bool nB); + // -Real dPrime2(const int64_t j, const int64_t k, const int64_t m, const Real t, const bool nB); + template bool testDerivatives(std::ostream& os); + + //@brief : run all wigner unit tests + //@param os: output stream to write error messages to + //@return : true/false if the self tests pass/fail + //@note : there isn't currently a unit test for rotateHarmonics + template bool runTests(std::ostream& os); + } + +} + + +int main() { + //select output stream + std::ostream& os = std::cout; + + //run unit tests + const bool passed = emsphinx::wigner::runTests(os) + && emsphinx::wigner::runTests(os); + + //return result + return passed ? EXIT_SUCCESS : EXIT_FAILURE; +} + +#include +#include + +#include "sht/wigner.hpp" + +namespace emsphinx { + + namespace wigner { + //@brief : test d^j_{k,m}(\beta) functions + //@param os: output stream to write error messages to + //@return : true/false if the functions are consistent with precomputed values from mathematica's WignerD[{j,k,m},\beta] + //@note : functions tested + // - Real d(const int64_t j, const int64_t k, const int64_t m, const Real t, const bool nB) + // - Real d(const int j, const int k, const int m) + // - int dSign(const int j, const int k, const int m) + // -std::complex D(const int64_t j, const int64_t k, const int64_t m, Real const * const eu) + template bool testDjkm(std::ostream& os) { + os << "testing single point wigner d functions\n"; + + //start by tabulating some values from mathematica + //these are from mathematica: N[ Table[WignerD[{j, k, m}, Pi/2], {j, 0L, x}, {k, -x, x}, {m, -x, x}] , 32] where x == Num-1 + const int Num = 5;//table size + + //beta = pi/2 + long double tab12[Num][2*Num+1][2*Num+1] = { // j, k, m with j in [0L,Num) and k/m in (-Num,Num) + {//j == 0 + // m == -4 , -3 , -2 , -1 , 0 , 1 , 2 , 3 , 4 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -4 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -3 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -2 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -1 + { NAN , NAN , NAN , NAN , 1.0000000000000000000000000000000L, NAN , NAN , NAN , NAN },// k == 0 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == 1 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == 2 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == 3 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } // k == 4 + },{//j == 1 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -4 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -3 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -2 + { NAN , NAN , NAN , 0.50000000000000000000000000000000L, -0.70710678118654752440084436210485L, 0.50000000000000000000000000000000L, NAN , NAN , NAN },// k == -1 + { NAN , NAN , NAN , 0.70710678118654752440084436210485L, 0.00000000000000000000000000000000L, -0.70710678118654752440084436210485L, NAN , NAN , NAN },// k == 0 + { NAN , NAN , NAN , 0.50000000000000000000000000000000L, 0.70710678118654752440084436210485L, 0.50000000000000000000000000000000L, NAN , NAN , NAN },// k == 1 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == 2 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == 3 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } // k == 4 + },{//j == 2 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -4 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -3 + { NAN , NAN , 0.25000000000000000000000000000000L, -0.50000000000000000000000000000000L, 0.61237243569579452454932101867647L, -0.50000000000000000000000000000000L, 0.25000000000000000000000000000000L, NAN , NAN },// k == -2 + { NAN , NAN , 0.50000000000000000000000000000000L, -0.50000000000000000000000000000000L, 0.00000000000000000000000000000000L, 0.50000000000000000000000000000000L, -0.50000000000000000000000000000000L, NAN , NAN },// k == -1 + { NAN , NAN , 0.61237243569579452454932101867647L, 0.00000000000000000000000000000000L, -0.50000000000000000000000000000000L, 0.00000000000000000000000000000000L, 0.61237243569579452454932101867647L, NAN , NAN },// k == 0 + { NAN , NAN , 0.50000000000000000000000000000000L, 0.50000000000000000000000000000000L, 0.00000000000000000000000000000000L, -0.50000000000000000000000000000000L, -0.50000000000000000000000000000000L, NAN , NAN },// k == 1 + { NAN , NAN , 0.25000000000000000000000000000000L, 0.50000000000000000000000000000000L, 0.61237243569579452454932101867647L, 0.50000000000000000000000000000000L, 0.25000000000000000000000000000000L, NAN , NAN },// k == 2 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == 3 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } // k == 4 + },{//j == 3 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -4 + { NAN , 0.12500000000000000000000000000000L, -0.30618621784789726227466050933824L, 0.48412291827592711064740817497280L, -0.55901699437494742410229341718282L, 0.48412291827592711064740817497280L, -0.30618621784789726227466050933824L, 0.12500000000000000000000000000000L, NAN },// k == -3 + { NAN , 0.30618621784789726227466050933824L, -0.50000000000000000000000000000000L, 0.39528470752104741649986169305409L, 0.00000000000000000000000000000000L, -0.39528470752104741649986169305409L, 0.50000000000000000000000000000000L, -0.30618621784789726227466050933824L, NAN },// k == -2 + { NAN , 0.48412291827592711064740817497280L, -0.39528470752104741649986169305409L, -0.12500000000000000000000000000000L, 0.43301270189221932338186158537647L, -0.12500000000000000000000000000000L, -0.39528470752104741649986169305409L, 0.48412291827592711064740817497280L, NAN },// k == -1 + { NAN , 0.55901699437494742410229341718282L, 0.00000000000000000000000000000000L, -0.43301270189221932338186158537647L, 0.00000000000000000000000000000000L, 0.43301270189221932338186158537647L, 0.00000000000000000000000000000000L, -0.55901699437494742410229341718282L, NAN },// k == 0 + { NAN , 0.48412291827592711064740817497280L, 0.39528470752104741649986169305409L, -0.12500000000000000000000000000000L, -0.43301270189221932338186158537647L, -0.12500000000000000000000000000000L, 0.39528470752104741649986169305409L, 0.48412291827592711064740817497280L, NAN },// k == 1 + { NAN , 0.30618621784789726227466050933824L, 0.50000000000000000000000000000000L, 0.39528470752104741649986169305409L, 0.00000000000000000000000000000000L, -0.39528470752104741649986169305409L, -0.50000000000000000000000000000000L, -0.30618621784789726227466050933824L, NAN },// k == 2 + { NAN , 0.12500000000000000000000000000000L, 0.30618621784789726227466050933824L, 0.48412291827592711064740817497280L, 0.55901699437494742410229341718282L, 0.48412291827592711064740817497280L, 0.30618621784789726227466050933824L, 0.12500000000000000000000000000000L, NAN },// k == 3 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } // k == 4 + },{//j == 4 + { 0.06250000000000000000000000000000L, -0.17677669529663688110021109052621L, 0.33071891388307382381270196920491L, -0.46770717334674267319796859153957L, 0.52291251658379721748635751611574L, -0.46770717334674267319796859153957L, 0.33071891388307382381270196920491L, -0.17677669529663688110021109052621L, 0.06250000000000000000000000000000L},// k == -4 + { 0.17677669529663688110021109052621L, -0.37500000000000000000000000000000L, 0.46770717334674267319796859153957L, -0.33071891388307382381270196920491L, 0.00000000000000000000000000000000L, 0.33071891388307382381270196920491L, -0.46770717334674267319796859153957L, 0.37500000000000000000000000000000L, -0.17677669529663688110021109052621L},// k == -3 + { 0.33071891388307382381270196920491L, -0.46770717334674267319796859153957L, 0.25000000000000000000000000000000L, 0.17677669529663688110021109052621L, -0.39528470752104741649986169305409L, 0.17677669529663688110021109052621L, 0.25000000000000000000000000000000L, -0.46770717334674267319796859153957L, 0.33071891388307382381270196920491L},// k == -2 + { 0.46770717334674267319796859153957L, -0.33071891388307382381270196920491L, -0.17677669529663688110021109052621L, 0.37500000000000000000000000000000L, 0.00000000000000000000000000000000L, -0.37500000000000000000000000000000L, 0.17677669529663688110021109052621L, 0.33071891388307382381270196920491L, -0.46770717334674267319796859153957L},// k == -1 + { 0.52291251658379721748635751611574L, 0.00000000000000000000000000000000L, -0.39528470752104741649986169305409L, 0.00000000000000000000000000000000L, 0.37500000000000000000000000000000L, 0.00000000000000000000000000000000L, -0.39528470752104741649986169305409L, 0.00000000000000000000000000000000L, 0.52291251658379721748635751611574L},// k == 0 + { 0.46770717334674267319796859153957L, 0.33071891388307382381270196920491L, -0.17677669529663688110021109052621L, -0.37500000000000000000000000000000L, 0.00000000000000000000000000000000L, 0.37500000000000000000000000000000L, 0.17677669529663688110021109052621L, -0.33071891388307382381270196920491L, -0.46770717334674267319796859153957L},// k == 1 + { 0.33071891388307382381270196920491L, 0.46770717334674267319796859153957L, 0.25000000000000000000000000000000L, -0.17677669529663688110021109052621L, -0.39528470752104741649986169305409L, -0.17677669529663688110021109052621L, 0.25000000000000000000000000000000L, 0.46770717334674267319796859153957L, 0.33071891388307382381270196920491L},// k == 2 + { 0.17677669529663688110021109052621L, 0.37500000000000000000000000000000L, 0.46770717334674267319796859153957L, 0.33071891388307382381270196920491L, 0.00000000000000000000000000000000L, -0.33071891388307382381270196920491L, -0.46770717334674267319796859153957L, -0.37500000000000000000000000000000L, -0.17677669529663688110021109052621L},// k == 3 + { 0.06250000000000000000000000000000L, 0.17677669529663688110021109052621L, 0.33071891388307382381270196920491L, 0.46770717334674267319796859153957L, 0.52291251658379721748635751611574L, 0.46770717334674267319796859153957L, 0.33071891388307382381270196920491L, 0.17677669529663688110021109052621L, 0.06250000000000000000000000000000L} // k == 4 + } + }; + + // \beta = pi / 3 + long double tab13[Num][2*Num+1][2*Num+1] = { + { //j == 0 + // m == -4 , -3 , -2 , -1 , 0 , 1 , 2 , 3 , 4 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -4 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -3 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -2 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -1 + { NAN , NAN , NAN , NAN , 1.00000000000000000000000000000000L, NAN , NAN , NAN , NAN },// k == 0 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == 1 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == 2 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == 3 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } // k == 4 + },{//j == 1 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -4 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -3 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -2 + { NAN , NAN , NAN , 0.75000000000000000000000000000000L, -0.61237243569579452454932101867647L, 0.25000000000000000000000000000000L, NAN , NAN , NAN },// k == -1 + { NAN , NAN , NAN , 0.61237243569579452454932101867647L, 0.50000000000000000000000000000000L, -0.61237243569579452454932101867647L, NAN , NAN , NAN },// k == 0 + { NAN , NAN , NAN , 0.25000000000000000000000000000000L, 0.61237243569579452454932101867647L, 0.75000000000000000000000000000000L, NAN , NAN , NAN },// k == 1 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == 2 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == 3 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } // k == 4 + },{//j == 2 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -4 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -3 + { NAN , NAN , 0.56250000000000000000000000000000L, -0.64951905283832898507279237806470L, 0.45927932677184589341199076400735L, -0.21650635094610966169093079268823L, 0.06250000000000000000000000000000L, NAN , NAN },// k == -2 + { NAN , NAN , 0.64951905283832898507279237806470L, 0.00000000000000000000000000000000L, -0.53033008588991064330063327157864L, 0.50000000000000000000000000000000L, -0.21650635094610966169093079268823L, NAN , NAN },// k == -1 + { NAN , NAN , 0.45927932677184589341199076400735L, 0.53033008588991064330063327157864L, -0.12500000000000000000000000000000L, -0.53033008588991064330063327157864L, 0.45927932677184589341199076400735L, NAN , NAN },// k == 0 + { NAN , NAN , 0.21650635094610966169093079268823L, 0.50000000000000000000000000000000L, 0.53033008588991064330063327157864L, 0.00000000000000000000000000000000L, -0.64951905283832898507279237806470L, NAN , NAN },// k == 1 + { NAN , NAN , 0.06250000000000000000000000000000L, 0.21650635094610966169093079268823L, 0.45927932677184589341199076400735L, 0.64951905283832898507279237806470L, 0.56250000000000000000000000000000L, NAN , NAN },// k == 2 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == 3 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } // k == 4 + },{//j == 3 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -4 + { NAN , 0.42187500000000000000000000000000L, -0.59662134662614947371321243052597L, 0.54463828306041799947833419684440L, -0.36309218870694533298555613122960L, 0.18154609435347266649277806561480L, -0.06629126073623883041257915894733L, 0.01562500000000000000000000000000L, NAN },// k == -3 + { NAN , 0.59662134662614947371321243052597L, -0.28125000000000000000000000000000L, -0.25674494883054661568295458568788L, 0.51348989766109323136590917137575L, -0.42790824805091102613825764281313L, 0.21875000000000000000000000000000L, -0.06629126073623883041257915894733L, NAN },// k == -2 + { NAN , 0.54463828306041799947833419684440L, 0.25674494883054661568295458568788L, -0.42187500000000000000000000000000L, -0.09375000000000000000000000000000L, 0.48437500000000000000000000000000L, -0.42790824805091102613825764281313L, 0.18154609435347266649277806561480L, NAN },// k == -1 + { NAN , 0.36309218870694533298555613122960L, 0.51348989766109323136590917137575L, 0.09375000000000000000000000000000L, -0.43750000000000000000000000000000L, -0.09375000000000000000000000000000L, 0.51348989766109323136590917137575L, -0.36309218870694533298555613122960L, NAN },// k == 0 + { NAN , 0.18154609435347266649277806561480L, 0.42790824805091102613825764281313L, 0.48437500000000000000000000000000L, 0.09375000000000000000000000000000L, -0.42187500000000000000000000000000L, -0.25674494883054661568295458568788L, 0.54463828306041799947833419684440L, NAN },// k == 1 + { NAN , 0.06629126073623883041257915894733L, 0.21875000000000000000000000000000L, 0.42790824805091102613825764281313L, 0.51348989766109323136590917137575L, 0.25674494883054661568295458568788L, -0.28125000000000000000000000000000L, -0.59662134662614947371321243052597L, NAN },// k == 2 + { NAN , 0.01562500000000000000000000000000L, 0.06629126073623883041257915894733L, 0.18154609435347266649277806561480L, 0.36309218870694533298555613122960L, 0.54463828306041799947833419684440L, 0.59662134662614947371321243052597L, 0.42187500000000000000000000000000L, NAN },// k == 3 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } // k == 4 + },{//j == 4 + { 0.31640625000000000000000000000000L, -0.51668924261832663008848960950827L, 0.55808816717768707768393457303328L, -0.45567708035680267248979458534994L, 0.29413829057838593483607610281510L, -0.15189236011893422416326486178331L, 0.06200979635307634196488161922592L, -0.01913663861549357889216628183364L, 0.00390625000000000000000000000000L},// k == -4 + { 0.51668924261832663008848960950827L, -0.42187500000000000000000000000000L, 0.00000000000000000000000000000000L, 0.37205877811845805178928971535552L, -0.48032581715435617421348618814942L, 0.37205877811845805178928971535552L, -0.20252314682524563221768648237775L, 0.07812500000000000000000000000000L, -0.01913663861549357889216628183364L},// k == -3 + { 0.55808816717768707768393457303328L, 0.00000000000000000000000000000000L, -0.42187500000000000000000000000000L, 0.22963966338592294670599538200368L, 0.22234764798058917178117220234293L, -0.45927932677184589341199076400735L, 0.39062500000000000000000000000000L, -0.20252314682524563221768648237775L, 0.06200979635307634196488161922592L},// k == -2 + { 0.45567708035680267248979458534994L, 0.37205877811845805178928971535552L, -0.22963966338592294670599538200368L, -0.32812500000000000000000000000000L, 0.30257682392245444415463010935800L, 0.17187500000000000000000000000000L, -0.45927932677184589341199076400735L, 0.37205877811845805178928971535552L, -0.15189236011893422416326486178331L},// k == -1 + { 0.29413829057838593483607610281510L, 0.48032581715435617421348618814942L, 0.22234764798058917178117220234293L, -0.30257682392245444415463010935800L, -0.28906250000000000000000000000000L, 0.30257682392245444415463010935800L, 0.22234764798058917178117220234293L, -0.48032581715435617421348618814942L, 0.29413829057838593483607610281510L},// k == 0 + { 0.15189236011893422416326486178331L, 0.37205877811845805178928971535552L, 0.45927932677184589341199076400735L, 0.17187500000000000000000000000000L, -0.30257682392245444415463010935800L, -0.32812500000000000000000000000000L, 0.22963966338592294670599538200368L, 0.37205877811845805178928971535552L, -0.45567708035680267248979458534994L},// k == 1 + { 0.06200979635307634196488161922592L, 0.20252314682524563221768648237775L, 0.39062500000000000000000000000000L, 0.45927932677184589341199076400735L, 0.22234764798058917178117220234293L, -0.22963966338592294670599538200368L, -0.42187500000000000000000000000000L, 0.00000000000000000000000000000000L, 0.55808816717768707768393457303328L},// k == 2 + { 0.01913663861549357889216628183364L, 0.07812500000000000000000000000000L, 0.20252314682524563221768648237775L, 0.37205877811845805178928971535552L, 0.48032581715435617421348618814942L, 0.37205877811845805178928971535552L, 0.00000000000000000000000000000000L, -0.42187500000000000000000000000000L, -0.51668924261832663008848960950827L},// k == 3 + { 0.00390625000000000000000000000000L, 0.01913663861549357889216628183364L, 0.06200979635307634196488161922592L, 0.15189236011893422416326486178331L, 0.29413829057838593483607610281510L, 0.45567708035680267248979458534994L, 0.55808816717768707768393457303328L, 0.51668924261832663008848960950827L, 0.31640625000000000000000000000000L} // k == 4 + } + }; + + // \beta = 2 * pi / 3 + long double tab23[Num][2*Num+1][2*Num+1] = { + {//j == 0 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -4 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -3 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -2 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -1 + { NAN , NAN , NAN , NAN , 1.00000000000000000000000000000000L, NAN , NAN , NAN , NAN },// k == 0 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == 1 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == 2 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == 3 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } // k == 4 + },{//j == 1 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -4 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -3 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -2 + { NAN , NAN , NAN , 0.25000000000000000000000000000000L, -0.61237243569579452454932101867647L, 0.75000000000000000000000000000000L, NAN , NAN , NAN },// k == -1 + { NAN , NAN , NAN , 0.61237243569579452454932101867647L, -0.50000000000000000000000000000000L, -0.61237243569579452454932101867647L, NAN , NAN , NAN },// k == 0 + { NAN , NAN , NAN , 0.75000000000000000000000000000000L, 0.61237243569579452454932101867647L, 0.25000000000000000000000000000000L, NAN , NAN , NAN },// k == 1 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == 2 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == 3 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } // k == 4 + },{//j == 2 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -4 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -3 + { NAN , NAN , 0.06250000000000000000000000000000L, -0.21650635094610966169093079268823L, 0.45927932677184589341199076400735L, -0.64951905283832898507279237806470L, 0.56250000000000000000000000000000L, NAN , NAN },// k == -2 + { NAN , NAN , 0.21650635094610966169093079268823L, -0.50000000000000000000000000000000L, 0.53033008588991064330063327157864L, 0.00000000000000000000000000000000L, -0.64951905283832898507279237806470L, NAN , NAN },// k == -1 + { NAN , NAN , 0.45927932677184589341199076400735L, -0.53033008588991064330063327157864L, -0.12500000000000000000000000000000L, 0.53033008588991064330063327157864L, 0.45927932677184589341199076400735L, NAN , NAN },// k == 0 + { NAN , NAN , 0.64951905283832898507279237806470L, 0.00000000000000000000000000000000L, -0.53033008588991064330063327157864L, -0.50000000000000000000000000000000L, -0.21650635094610966169093079268823L, NAN , NAN },// k == 1 + { NAN , NAN , 0.56250000000000000000000000000000L, 0.64951905283832898507279237806470L, 0.45927932677184589341199076400735L, 0.21650635094610966169093079268823L, 0.06250000000000000000000000000000L, NAN , NAN },// k == 2 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == 3 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } // k == 4 + },{//j == 3 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN },// k == -4 + { NAN , 0.01562500000000000000000000000000L, -0.06629126073623883041257915894733L, 0.18154609435347266649277806561480L, -0.36309218870694533298555613122960L, 0.54463828306041799947833419684440L, -0.59662134662614947371321243052597L, 0.42187500000000000000000000000000L, NAN },// k == -3 + { NAN , 0.06629126073623883041257915894733L, -0.21875000000000000000000000000000L, 0.42790824805091102613825764281313L, -0.51348989766109323136590917137575L, 0.25674494883054661568295458568788L, 0.28125000000000000000000000000000L, -0.59662134662614947371321243052597L, NAN },// k == -2 + { NAN , 0.18154609435347266649277806561480L, -0.42790824805091102613825764281313L, 0.48437500000000000000000000000000L, -0.09375000000000000000000000000000L, -0.42187500000000000000000000000000L, 0.25674494883054661568295458568788L, 0.54463828306041799947833419684440L, NAN },// k == -1 + { NAN , 0.36309218870694533298555613122960L, -0.51348989766109323136590917137575L, 0.09375000000000000000000000000000L, 0.43750000000000000000000000000000L, -0.09375000000000000000000000000000L, -0.51348989766109323136590917137575L, -0.36309218870694533298555613122960L, NAN },// k == 0 + { NAN , 0.54463828306041799947833419684440L, -0.25674494883054661568295458568788L, -0.42187500000000000000000000000000L, 0.09375000000000000000000000000000L, 0.48437500000000000000000000000000L, 0.42790824805091102613825764281313L, 0.18154609435347266649277806561480L, NAN },// k == 1 + { NAN , 0.59662134662614947371321243052597L, 0.28125000000000000000000000000000L, -0.25674494883054661568295458568788L, -0.51348989766109323136590917137575L, -0.42790824805091102613825764281313L, -0.21875000000000000000000000000000L, -0.06629126073623883041257915894733L, NAN },// k == 2 + { NAN , 0.42187500000000000000000000000000L, 0.59662134662614947371321243052597L, 0.54463828306041799947833419684440L, 0.36309218870694533298555613122960L, 0.18154609435347266649277806561480L, 0.06629126073623883041257915894733L, 0.01562500000000000000000000000000L, NAN },// k == 3 + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } // k == 4 + },{//j == 4 + { 0.00390625000000000000000000000000L, -0.01913663861549357889216628183364L, 0.06200979635307634196488161922592L, -0.15189236011893422416326486178331L, 0.29413829057838593483607610281510L, -0.45567708035680267248979458534994L, 0.55808816717768707768393457303328L, -0.51668924261832663008848960950827L, 0.31640625000000000000000000000000L},// k == -4 + { 0.01913663861549357889216628183364L, -0.07812500000000000000000000000000L, 0.20252314682524563221768648237775L, -0.37205877811845805178928971535552L, 0.48032581715435617421348618814942L, -0.37205877811845805178928971535552L, 0.00000000000000000000000000000000L, 0.42187500000000000000000000000000L, -0.51668924261832663008848960950827L},// k == -3 + { 0.06200979635307634196488161922592L, -0.20252314682524563221768648237775L, 0.39062500000000000000000000000000L, -0.45927932677184589341199076400735L, 0.22234764798058917178117220234293L, 0.22963966338592294670599538200368L, -0.42187500000000000000000000000000L, 0.00000000000000000000000000000000L, 0.55808816717768707768393457303328L},// k == -2 + { 0.15189236011893422416326486178331L, -0.37205877811845805178928971535552L, 0.45927932677184589341199076400735L, -0.17187500000000000000000000000000L, -0.30257682392245444415463010935800L, 0.32812500000000000000000000000000L, 0.22963966338592294670599538200368L, -0.37205877811845805178928971535552L, -0.45567708035680267248979458534994L},// k == -1 + { 0.29413829057838593483607610281510L, -0.48032581715435617421348618814942L, 0.22234764798058917178117220234293L, 0.30257682392245444415463010935800L, -0.28906250000000000000000000000000L, -0.30257682392245444415463010935800L, 0.22234764798058917178117220234293L, 0.48032581715435617421348618814942L, 0.29413829057838593483607610281510L},// k == 0 + { 0.45567708035680267248979458534994L, -0.37205877811845805178928971535552L, -0.22963966338592294670599538200368L, 0.32812500000000000000000000000000L, 0.30257682392245444415463010935800L, -0.17187500000000000000000000000000L, -0.45927932677184589341199076400735L, -0.37205877811845805178928971535552L, -0.15189236011893422416326486178331L},// k == 1 + { 0.55808816717768707768393457303328L, 0.00000000000000000000000000000000L, -0.42187500000000000000000000000000L, -0.22963966338592294670599538200368L, 0.22234764798058917178117220234293L, 0.45927932677184589341199076400735L, 0.39062500000000000000000000000000L, 0.20252314682524563221768648237775L, 0.06200979635307634196488161922592L},// k == 2 + { 0.51668924261832663008848960950827L, 0.42187500000000000000000000000000L, 0.00000000000000000000000000000000L, -0.37205877811845805178928971535552L, -0.48032581715435617421348618814942L, -0.37205877811845805178928971535552L, -0.20252314682524563221768648237775L, -0.07812500000000000000000000000000L, -0.01913663861549357889216628183364L},// k == 3 + { 0.31640625000000000000000000000000L, 0.51668924261832663008848960950827L, 0.55808816717768707768393457303328L, 0.45567708035680267248979458534994L, 0.29413829057838593483607610281510L, 0.15189236011893422416326486178331L, 0.06200979635307634196488161922592L, 0.01913663861549357889216628183364L, 0.00390625000000000000000000000000L} // k == 4 + } + }; + + //check d^j_{k,m} functions against table + const Real eps = std::numeric_limits::epsilon() * 2; + for(int j = 0; j < Num; j++) { + for(int k = 1-Num; k < Num; k++) { + const size_t ik = k + Num-1;// (-Num,Num) ==> [0L,2*Num+1) + for(int m = 1-Num; m < Num; m++) { + const size_t im = m + Num-1;// (-Num,Num) ==> [0L,2*Num+1) + + //compute values + const Real dVals[6] = { + d(j, k, m ),//special function for d^j_{k,m}( \pi / 2) + d(j, k, m, 0.0L, false),//general function for d^j_{k,m}( \pi / 2) + d(j, k, m, 0.5L, false),//general function for d^j_{k,m}( \pi / 3) + d(j, k, m, 0.5L, true ),//general function for d^j_{k,m}(- \pi / 3) + d(j, k, m, -0.5L, false),//general function for d^j_{k,m}( 2\pi / 3) + d(j, k, m, -0.5L, true ),//general function for d^j_{k,m}(-2\pi / 3) + }; + + //pull values from table + const Real tVals[6] = { + static_cast(tab12[j][ik][im]), + static_cast(tab12[j][ik][im]), + static_cast(tab13[j][ik][im]), + static_cast(tab13[j][im][ik]),//d^j_{ k, m}(-beta) = d^j_{m,k}(beta) + static_cast(tab23[j][ik][im]), + static_cast(tab23[j][im][ik]),//d^j_{ k, m}(-beta) = d^j_{m,k}(beta) + }; + + //compare values + for(size_t i = 0; i < 6; i++) { + if(std::isnan(dVals[i]) && std::isnan(tVals[i])) continue;//both are undefined + const double delta = std::fabs(dVals[i] - tVals[i]);//compute difference + if(delta > eps) { + std::string b;//beta string + switch(i) { + case 0: //intentional fall through + case 1: b = "Pi/2"; break; + case 2: b = "Pi/3"; break; + case 3: b = "-Pi/3"; break; + case 4: b = "2*Pi/3"; break; + case 5: b = "-2*Pi/3"; break; + } + os << "mismatch for d^j_{k,m} with WignerD[{" << j << ", " << k << ", " << m << "}, " << b << "]:\n"; + os << std::setprecision(std::numeric_limits::digits10); + os << "\ttable: " << tVals[i] << '\n'; + os << "\tfunc" << i << ": " << dVals[i] << '\n'; + os << "\tdelta: " << delta << '\n'; + os << "\tlimit: " << eps << '\n'; + os.flush(); + return false; + } + } + } + } + } + + //check dSign + for(int j = 0; j < Num; j++) { + for(int k = 1-Num; k < Num; k++) { + const size_t ik = k + Num-1;// (-Num,Num) ==> [0L,2*Num+1) + const size_t pk = std::abs(k) + Num-1;// (-Num,Num) ==> [0L,2*Num+1) + for(int m = 1-Num; m < Num; m++) { + const size_t im = m + Num-1;// (-Num,Num) ==> [0L,2*Num+1) + const size_t pm = std::abs(m) + Num-1;// (-Num,Num) ==> [0L,2*Num+1) + + //fetch d^j_{k,m} from the table + const Real vD = static_cast(tab12[j][ik][im]); + + //fetch d^j_{|k|,|m|} from the table + const int sgn = dSign(j, k, m); + const Real vA = static_cast(tab12[j][pk][pm]) * sgn; + + //check dSign + if(std::isnan(vD) && std::isnan(vA)) continue;//both are undefined + if(vD != vA) { + os << "mismatch for dSign(" << j << ", " << k << ", " << m << ") * d^" << j << "_{|" << k << "|, |" << m << "|} "; + os << "vs d^" << j << "_{" << k << ", " << m << "}:\n"; + os << "\td^" << j << "_{" << k << ", " << m << "}: " << vD << '\n'; + os << "\tabs * dSign: " << vA << '\n'; + os.flush(); + return false; + } + } + } + } + + //check D[{j,k,m},\alpha, \beta, \gamma] + const Real pi6 = static_cast(0.52359877559829887307710723054658L);// Pi/6 + const Real pi3 = static_cast(1.04719755119659774615421446109317L);// Pi/3 + const Real pi2 = static_cast(1.57079632679489661923132169163975L);// Pi/2 + const std::complex expPi6(static_cast(0.86602540378443864676372317075294L), static_cast(0.50000000000000000000000000000000L));//exp ^ {I * pi / 6L} + const std::complex expPi3(static_cast(0.50000000000000000000000000000000L), static_cast(0.86602540378443864676372317075294L));//exp ^ {I * pi / 3L} + const Real eu[3] = {pi3, pi2, pi6}; + const std::complex d321(static_cast(0.19764235376052370824993084652704L), static_cast(-0.34232659844072882091060611425050L));// WignerD[{3L, 2L, 1L}, Pi/6L, Pi/2L, Pi/3] + const std::complex res = D(3L,2L,1L,eu); + const Real delta = std::abs(d321 - res); + if(delta > eps) { + os << "mismatch for D^3_{2L,1L}(pi/3L, pi/2L, pi/6) and WignerD[{3L, 2L, 1L}, Pi/6L, Pi/2L, Pi/3]:\n"; + os << "\tD^3_{2L,1L}(pi/3L, pi/2L, pi/6): " << res << '\n'; + os << "\tWignerD[{3L, 2L, 1L}, Pi/6L, Pi/2L, Pi/3]: " << d321 << '\n'; + os << "\tdelta: " << delta << '\n'; + os << "\tlimit: " << eps << '\n'; + os.flush(); + return false; + } + + //if we made it this far all tests passed + return true; + } + + //@brief : test all the table functions + //@param j : max degree in (d/dBeta)^2 d^j_{k,m}(beta) + //@param os: output stream to write error messages to + //@return : true/false if the table functions are consistent with the single point recursive functions + //@note : functions tested + // -void dTable(const size_t jMax, const Real t, const bool nB, Real * const table) + // -void dTablePre(const size_t jMax, const Real t, const bool nB, Real * const table, Real const * const pE, Real const * const pW, Real const * const pB) + // -void dTablePreBuild(const size_t jMax, Real * const pE, Real * const pW, Real * const pB) + // -void dTable(const size_t jMax, Real * const table, const bool trans = false) + template bool testTables(const int64_t bw, std::ostream& os) { + os << "testing wigner d tables\n"; + + //allocate lookup table space one + std::vector table(bw * bw * bw * 2); + + //select a random angle that isn't a special point w.r.t. symmetry + const Real beta = static_cast(0.9708055194);//pi / 2 / golden ratio + const Real t = std::cos(beta); + + //test dTable(const size_t bw, const Real t, const bool nB, Real * const table) for nB = false + dTable(bw, t, false, table.data()); + for(int64_t k = 0; k < bw; k++) { + for(int64_t m = 0; m < bw; m++) { + for(int64_t j = 0; j < bw; j++) { + const Real dP = d(j, k, m, t, false);//function evaluation + const Real dN = d(j, k, m, -t, false);//function evaluation + const Real& tP = table[(k * bw * bw + m * bw + j)*2 + 0];//table value + const Real& tN = table[(k * bw * bw + m * bw + j)*2 + 1];//table value + const Real pMatch = dP == tP || std::isnan(dP); + const Real nMatch = dN == tN || std::isnan(dN); + if(!pMatch || !nMatch) { + os << "table mismatch for `dTable(const size_t bw, const Real t, const bool nB, Real * const table)'\n"; + os << "with (j,k,m,t) == (" << j << ',' << k << ',' << m << ",+/-" << t << ") and nB == false\n"; + os << "\ttable\tfunction\n"; + os << '\t' << tP << '\t' << dP << "\tpositive t" << '\n'; + os << '\t' << tN << '\t' << dN << "\tnegative t" << '\n'; + os.flush(); + return false; + } + } + } + } + + //test dTable(const size_t bw, const Real t, const bool nB, Real * const table) for nB = true + dTable(bw, t, true , table.data()); + for(int64_t k = 0; k < bw; k++) { + for(int64_t m = 0; m < bw; m++) { + for(int64_t j = 0; j < bw; j++) { + const Real dP = d(j, k, m, t, true );//function evaluation + const Real dN = d(j, k, m, -t, true );//function evaluation + const Real& tP = table[(k * bw * bw + m * bw + j)*2 + 0];//table value + const Real& tN = table[(k * bw * bw + m * bw + j)*2 + 1];//table value + const Real pMatch = dP == tP || std::isnan(dP); + const Real nMatch = dN == tN || std::isnan(dN); + if(!pMatch || !nMatch) { + os << "table mismatch for `dTable(const size_t bw, const Real t, const bool nB, Real * const table)'\n"; + os << "with (j,k,m,t) == (" << j << ',' << k << ',' << m << ",+/-" << t << ") and nB == true \n"; + os << "\ttable\tfunction\n"; + os << '\t' << tP << '\t' << dP << "\tpositive t" << '\n'; + os << '\t' << tN << '\t' << dN << "\tnegative t" << '\n'; + os.flush(); + return false; + } + } + } + } + + //build prefactors for dTablePre + std::vector pE(bw * bw * bw), pW(bw * bw * bw), pB(bw * bw * bw); + dTablePreBuild(bw, pE.data(), pW.data(), pB.data()); + + //test dTablePre(const size_t jMax, const Real t, const bool nB, Real * const table, Real const * const pE, Real const * const pW, Real const * const pB) for nB = false + dTablePre(bw, t, false, table.data(), pE.data(), pW.data(), pB.data()); + for(int64_t k = 0; k < bw; k++) { + for(int64_t m = 0; m < bw; m++) { + for(int64_t j = 0; j < bw; j++) { + const Real dP = d(j, k, m, t, false);//function evaluation + const Real dN = d(j, k, m, -t, false);//function evaluation + const Real& tP = table[(k * bw * bw + m * bw + j)*2 + 0];//table value + const Real& tN = table[(k * bw * bw + m * bw + j)*2 + 1];//table value + const Real pMatch = dP == tP || std::isnan(dP); + const Real nMatch = dN == tN || std::isnan(dN); + if(!pMatch || !nMatch) { + os << "table mismatch for `dTablePre(const size_t jMax, const Real t, const bool nB, Real * const table, Real const * const pE, Real const * const pW, Real const * const pB)'\n"; + os << "with (j,k,m,t) == (" << j << ',' << k << ',' << m << ",+/-" << t << ") and nB == false\n"; + os << "\ttable\tfunction\n"; + os << '\t' << tP << '\t' << dP << "\tpositive t" << '\n'; + os << '\t' << tN << '\t' << dN << "\tnegative t" << '\n'; + os.flush(); + return false; + } + } + } + } + + //test dTablePre(const size_t jMax, const Real t, const bool nB, Real * const table, Real const * const pE, Real const * const pW, Real const * const pB) for nB = true + dTablePre(bw, t, true , table.data(), pE.data(), pW.data(), pB.data()); + for(int64_t k = 0; k < bw; k++) { + for(int64_t m = 0; m < bw; m++) { + for(int64_t j = 0; j < bw; j++) { + const Real dP = d(j, k, m, t, true );//function evaluation + const Real dN = d(j, k, m, -t, true );//function evaluation + const Real& tP = table[(k * bw * bw + m * bw + j)*2 + 0];//table value + const Real& tN = table[(k * bw * bw + m * bw + j)*2 + 1];//table value + const Real pMatch = dP == tP || std::isnan(dP); + const Real nMatch = dN == tN || std::isnan(dN); + if(!pMatch || !nMatch) { + os << "table mismatch for `dTablePre(const size_t jMax, const Real t, const bool nB, Real * const table, Real const * const pE, Real const * const pW, Real const * const pB)'\n"; + os << "with (j,k,m,t) == (" << j << ',' << k << ',' << m << ",+/-" << t << ") and nB == true \n"; + os << "\ttable\tfunction\n"; + os << '\t' << tP << '\t' << dP << "\tpositive t" << '\n'; + os << '\t' << tN << '\t' << dN << "\tnegative t" << '\n'; + os.flush(); + return false; + } + } + } + } + + //test dTable(const size_t jMax, Real * const table, const bool trans = false); for trans = false + dTable(bw, table.data(), false); + for(int64_t k = 0; k < bw; k++) { + for(int64_t m = 0; m < bw; m++) { + for(int64_t j = 0; j < bw; j++) { + const Real dV = d(j, k, m);//function evaluation + const Real& tV = table[k * bw * bw + m * bw + j];//table value + if(dV != tV && !std::isnan(dV)) { + os << "table mismatch for `dTable(const size_t jMax, Real * const table, const bool trans = false)'\n"; + os << "with (j,k,m) == (" << j << ',' << k << ',' << m << ") and trans == false\n"; + os << "\ttable\tfunction\n"; + os << '\t' << tV << '\t' << dV << '\n'; + os.flush(); + return false; + } + } + } + } + + //test dTable(const size_t jMax, Real * const table, const bool trans = false); for trans = true + dTable(bw, table.data(), true ); + for(int64_t k = 0; k < bw; k++) { + for(int64_t m = 0; m < bw; m++) { + for(int64_t j = 0; j < bw; j++) { + const Real dV = d(j, k, m);//function evaluation + const Real& tV = table[m * bw * bw + k * bw + j];//table value + if(dV != tV && !std::isnan(dV)) { + os << "table mismatch for `dTable(const size_t jMax, Real * const table, const bool trans = false)'\n"; + os << "with (j,k,m) == (" << j << ',' << k << ',' << m << ") and trans == false\n"; + os << "\ttable\tfunction\n"; + os << '\t' << tV << '\t' << dV << '\n'; + os.flush(); + return false; + } + } + } + } + + //if we made it this far all table tests passed + return true; + } + + //@brief : test functions for derivatives of d^j_{k,m}(\beta) + //@param os: output stream to write error messages to + //@return : true/false if the functions are consistent with precomputed values from mathematica's D[WignerD[{j,k,m},\beta],{\beta, (1 or 2)}] + //@note : functions tested + // -Real dPrime(const int64_t j, const int64_t k, const int64_t m, const Real t, const bool nB); + // -Real dPrime2(const int64_t j, const int64_t k, const int64_t m, const Real t, const bool nB); + template bool testDerivatives(std::ostream& os) { + os << "testing wigner d derivatives\n"; + + //start by tabulating some values from mathematica + //these are from mathematica: + //Table[If[Max[Abs[k], Abs[m]] > j, NAN, D[WignerD[{j, k, m}, beta], {beta, 1L}]], {j, 0L, x}, {k, -x, x}, {m, -x, x}] + // where x == Num-1 + const int Num = 5;//table size + + // first derivative for \beta = pi / 3 + long double tab13_1[Num][2*Num+1][2*Num+1] = { // j, k, m with j in [0L,Num) and k/m in (-Num,Num) + { + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , 0.00000000000000000000000000000000L, NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } + },{ + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , -0.43301270189221932338186158537647L, -0.35355339059327376220042218105242L, 0.43301270189221932338186158537647L, NAN , NAN , NAN }, + { NAN , NAN , NAN , 0.35355339059327376220042218105242L, -0.86602540378443864676372317075294L, -0.35355339059327376220042218105242L, NAN , NAN , NAN }, + { NAN , NAN , NAN , 0.43301270189221932338186158537647L, 0.35355339059327376220042218105242L, -0.43301270189221932338186158537647L, NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } + },{ + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , -0.64951905283832898507279237806470L, 0.00000000000000000000000000000000L, 0.53033008588991064330063327157864L, -0.50000000000000000000000000000000L, 0.21650635094610966169093079268823L, NAN , NAN }, + { NAN , NAN , 0.00000000000000000000000000000000L, -1.29903810567665797014558475612940L, 0.61237243569579452454932101867647L, 0.43301270189221932338186158537647L, -0.50000000000000000000000000000000L, NAN , NAN }, + { NAN , NAN , 0.53033008588991064330063327157864L, -0.61237243569579452454932101867647L, -1.29903810567665797014558475612940L, 0.61237243569579452454932101867647L, 0.53033008588991064330063327157864L, NAN , NAN }, + { NAN , NAN , 0.50000000000000000000000000000000L, 0.43301270189221932338186158537647L, -0.61237243569579452454932101867647L, -1.29903810567665797014558475612940L, 0.00000000000000000000000000000000L, NAN , NAN }, + { NAN , NAN , 0.21650635094610966169093079268823L, 0.50000000000000000000000000000000L, 0.53033008588991064330063327157864L, 0.00000000000000000000000000000000L, -0.64951905283832898507279237806470L, NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } + },{ + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , -0.73070893444312010820689142532279L, 0.34445949507888442005899307300552L, 0.31444705933590792605754004716534L, -0.62889411867181585211508009433067L, 0.52407843222651321009590007860889L, -0.26791294061691010449032794567096L, 0.08118988160479112313409904725809L, NAN }, + { NAN , -0.34445949507888442005899307300552L, -1.13665834246707572387738666161320L, 1.33408588788353503068703321405760L, -0.29646353064078556237489626979057L, -0.54351647284144019768730982794937L, 0.59539246510180156965005967989264L, -0.26791294061691010449032794567096L, NAN }, + { NAN , 0.31444705933590792605754004716534L, -1.33408588788353503068703321405760L, -0.56832917123353786193869333080661L, 1.56967104435929504725924824698970L, -0.51420258349701044651596063263456L, -0.54351647284144019768730982794937L, 0.52407843222651321009590007860889L, NAN }, + { NAN , 0.62889411867181585211508009433067L, -0.29646353064078556237489626979057L, -1.56967104435929504725924824698970L, -0.32475952641916449253639618903235L, 1.56967104435929504725924824698970L, -0.29646353064078556237489626979057L, -0.62889411867181585211508009433067L, NAN }, + { NAN , 0.52407843222651321009590007860889L, 0.54351647284144019768730982794937L, -0.51420258349701044651596063263456L, -1.56967104435929504725924824698970L, -0.56832917123353786193869333080661L, 1.33408588788353503068703321405760L, 0.31444705933590792605754004716534L, NAN }, + { NAN , 0.26791294061691010449032794567096L, 0.59539246510180156965005967989264L, 0.54351647284144019768730982794937L, -0.29646353064078556237489626979057L, -1.33408588788353503068703321405760L, -1.13665834246707572387738666161320L, 0.34445949507888442005899307300552L, NAN }, + { NAN , 0.08118988160479112313409904725809L, 0.26791294061691010449032794567096L, 0.52407843222651321009590007860889L, 0.62889411867181585211508009433067L, 0.31444705933590792605754004716534L, -0.34445949507888442005899307300552L, -0.73070893444312010820689142532279L, NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } + },{ + {-0.73070893444312010820689142532279L, 0.59662134662614947371321243052597L, 0.00000000000000000000000000000000L, -0.52617057001508550734771466548201L, 0.67928328497762993314306238236733L, -0.52617057001508550734771466548201L, 0.28641098093474000041175294960800L, -0.11048543456039805068763193157888L, 0.02706329386826370771136634908603L}, + {-0.59662134662614947371321243052597L, -0.73070893444312010820689142532279L, 1.57851171004525652204314399644600L, -1.07404117850527500154407356103000L, 0.00000000000000000000000000000000L, 0.64442470710316500092644413661800L, -0.64309736335177117564720681336691L, 0.35182282028742820024776253811838L, -0.11048543456039805068763193157888L}, + { 0.00000000000000000000000000000000L, -1.57851171004525652204314399644600L, 0.48713928962874673880459428354853L, 1.39211647546101543866416233789390L, -1.54046969298327969409772751412730L, 0.33145630368119415206289579473665L, 0.59539246510180156965005967989264L, -0.64309736335177117564720681336691L, 0.28641098093474000041175294960800L}, + { 0.52617057001508550734771466548201L, -1.07404117850527500154407356103000L, -1.39211647546101543866416233789390L, 1.16372163633533943158875301069930L, 1.11803398874989484820458683436560L, -1.65086092596408617039334729424780L, 0.33145630368119415206289579473665L, 0.64442470710316500092644413661800L, -0.52617057001508550734771466548201L}, + { 0.67928328497762993314306238236733L, 0.00000000000000000000000000000000L, -1.54046969298327969409772751412730L, -1.11803398874989484820458683436560L, 1.35316469341318538556831745430150L, 1.11803398874989484820458683436560L, -1.54046969298327969409772751412730L, 0.00000000000000000000000000000000L, 0.67928328497762993314306238236733L}, + { 0.52617057001508550734771466548201L, 0.64442470710316500092644413661800L, -0.33145630368119415206289579473665L, -1.65086092596408617039334729424780L, -1.11803398874989484820458683436560L, 1.16372163633533943158875301069930L, 1.39211647546101543866416233789390L, -1.07404117850527500154407356103000L, -0.52617057001508550734771466548201L}, + { 0.28641098093474000041175294960800L, 0.64309736335177117564720681336691L, 0.59539246510180156965005967989264L, -0.33145630368119415206289579473665L, -1.54046969298327969409772751412730L, -1.39211647546101543866416233789390L, 0.48713928962874673880459428354853L, 1.57851171004525652204314399644600L, 0.00000000000000000000000000000000L}, + { 0.11048543456039805068763193157888L, 0.35182282028742820024776253811838L, 0.64309736335177117564720681336691L, 0.64442470710316500092644413661800L, 0.00000000000000000000000000000000L, -1.07404117850527500154407356103000L, -1.57851171004525652204314399644600L, -0.73070893444312010820689142532279L, 0.59662134662614947371321243052597L}, + { 0.02706329386826370771136634908603L, 0.11048543456039805068763193157888L, 0.28641098093474000041175294960800L, 0.52617057001508550734771466548201L, 0.67928328497762993314306238236733L, 0.52617057001508550734771466548201L, 0.00000000000000000000000000000000L, -0.59662134662614947371321243052597L, -0.73070893444312010820689142532279L} + } + }; + + // first derivative for \beta = 2 * pi / 3 + long double tab23_1[Num][2*Num+1][2*Num+1] = { + { + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , 0.00000000000000000000000000000000L, NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } + },{ + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , -0.43301270189221932338186158537647L, 0.35355339059327376220042218105242L, 0.43301270189221932338186158537647L, NAN , NAN , NAN }, + { NAN , NAN , NAN , -0.35355339059327376220042218105242L, -0.86602540378443864676372317075294L, 0.35355339059327376220042218105242L, NAN , NAN , NAN }, + { NAN , NAN , NAN , 0.43301270189221932338186158537647L, -0.35355339059327376220042218105242L, -0.43301270189221932338186158537647L, NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } + },{ + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , -0.21650635094610966169093079268823L, 0.50000000000000000000000000000000L, -0.53033008588991064330063327157864L, 0.00000000000000000000000000000000L, 0.64951905283832898507279237806470L, NAN , NAN }, + { NAN , NAN , -0.50000000000000000000000000000000L, 0.43301270189221932338186158537647L, 0.61237243569579452454932101867647L, -1.29903810567665797014558475612940L, 0.00000000000000000000000000000000L, NAN , NAN }, + { NAN , NAN , -0.53033008588991064330063327157864L, -0.61237243569579452454932101867647L, 1.2990381056766579701455847561294L, 0.612372435695794524549321018676470L, -0.53033008588991064330063327157864L, NAN , NAN }, + { NAN , NAN , 0.00000000000000000000000000000000L, -1.2990381056766579701455847561294L, -0.61237243569579452454932101867647L, 0.433012701892219323381861585376470L, 0.50000000000000000000000000000000L, NAN , NAN }, + { NAN , NAN , 0.64951905283832898507279237806470L, 0.00000000000000000000000000000000L, -0.53033008588991064330063327157864L, -0.50000000000000000000000000000000L, -0.21650635094610966169093079268823L, NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } + },{ + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , -0.08118988160479112313409904725809L, 0.26791294061691010449032794567096L, -0.52407843222651321009590007860889L, 0.62889411867181585211508009433067L, -0.31444705933590792605754004716534L, -0.34445949507888442005899307300552L, 0.73070893444312010820689142532279L, NAN }, + { NAN , -0.26791294061691010449032794567096L, 0.59539246510180156965005967989264L, -0.54351647284144019768730982794937L, -0.29646353064078556237489626979057L, 1.33408588788353503068703321405760L, -1.13665834246707572387738666161320L, -0.34445949507888442005899307300552L, NAN }, + { NAN , -0.52407843222651321009590007860889L, 0.54351647284144019768730982794937L, 0.51420258349701044651596063263456L, -1.56967104435929504725924824698970L, 0.56832917123353786193869333080661L, 1.33408588788353503068703321405760L, -0.31444705933590792605754004716534L, NAN }, + { NAN , -0.62889411867181585211508009433067L, -0.29646353064078556237489626979057L, 1.56967104435929504725924824698970L, -0.32475952641916449253639618903235L, -1.56967104435929504725924824698970L, -0.29646353064078556237489626979057L, 0.62889411867181585211508009433067L, NAN }, + { NAN , -0.31444705933590792605754004716534L, -1.33408588788353503068703321405760L, 0.56832917123353786193869333080661L, 1.56967104435929504725924824698970L, 0.51420258349701044651596063263456L, -0.54351647284144019768730982794937L, -0.52407843222651321009590007860889L, NAN }, + { NAN , 0.34445949507888442005899307300552L, -1.13665834246707572387738666161320L, -1.33408588788353503068703321405760L, -0.29646353064078556237489626979057L, 0.54351647284144019768730982794937L, 0.59539246510180156965005967989264L, 0.26791294061691010449032794567096L, NAN }, + { NAN , 0.73070893444312010820689142532279L, 0.34445949507888442005899307300552L, -0.31444705933590792605754004716534L, -0.62889411867181585211508009433067L, -0.52407843222651321009590007860889L, -0.26791294061691010449032794567096L, -0.08118988160479112313409904725809L, NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } + },{ + {-0.02706329386826370771136634908603L, 0.11048543456039805068763193157888L, -0.28641098093474000041175294960800L, 0.52617057001508550734771466548201L, -0.67928328497762993314306238236733L, 0.52617057001508550734771466548201L, 0.00000000000000000000000000000000L, -0.59662134662614947371321243052597L, 0.73070893444312010820689142532279L}, + {-0.11048543456039805068763193157888L, 0.35182282028742820024776253811838L, -0.64309736335177117564720681336691L, 0.64442470710316500092644413661800L, 0.00000000000000000000000000000000L, -1.07404117850527500154407356103000L, 1.57851171004525652204314399644600L, -0.73070893444312010820689142532279L, -0.59662134662614947371321243052597L}, + {-0.28641098093474000041175294960800L, 0.64309736335177117564720681336691L, -0.59539246510180156965005967989264L, -0.33145630368119415206289579473665L, 1.54046969298327969409772751412730L, -1.39211647546101543866416233789390L, -0.48713928962874673880459428354853L, 1.57851171004525652204314399644600L, 0.00000000000000000000000000000000L}, + {-0.52617057001508550734771466548201L, 0.64442470710316500092644413661800L, 0.33145630368119415206289579473665L, -1.65086092596408617039334729424780L, 1.11803398874989484820458683436560L, 1.16372163633533943158875301069930L, -1.39211647546101543866416233789390L, -1.07404117850527500154407356103000L, 0.52617057001508550734771466548201L}, + {-0.67928328497762993314306238236733L, 0.00000000000000000000000000000000L, 1.54046969298327969409772751412730L, -1.11803398874989484820458683436560L, -1.35316469341318538556831745430150L, 1.11803398874989484820458683436560L, 1.54046969298327969409772751412730L, 0.00000000000000000000000000000000L, -0.67928328497762993314306238236733L}, + {-0.52617057001508550734771466548201L, -1.07404117850527500154407356103000L, 1.39211647546101543866416233789390L, 1.16372163633533943158875301069930L, -1.11803398874989484820458683436560L, -1.65086092596408617039334729424780L, -0.33145630368119415206289579473665L, 0.64442470710316500092644413661800L, 0.52617057001508550734771466548201L}, + { 0.00000000000000000000000000000000L, -1.57851171004525652204314399644600L, -0.48713928962874673880459428354853L, 1.39211647546101543866416233789390L, 1.54046969298327969409772751412730L, 0.33145630368119415206289579473665L, -0.59539246510180156965005967989264L, -0.64309736335177117564720681336691L, -0.28641098093474000041175294960800L}, + { 0.59662134662614947371321243052597L, -0.73070893444312010820689142532279L, -1.57851171004525652204314399644600L, -1.07404117850527500154407356103000L, 0.00000000000000000000000000000000L, 0.64442470710316500092644413661800L, 0.64309736335177117564720681336691L, 0.35182282028742820024776253811838L, 0.11048543456039805068763193157888L}, + { 0.73070893444312010820689142532279L, 0.59662134662614947371321243052597L, 0.00000000000000000000000000000000L, -0.52617057001508550734771466548201L, -0.67928328497762993314306238236733L, -0.52617057001508550734771466548201L, -0.28641098093474000041175294960800L, -0.11048543456039805068763193157888L, -0.02706329386826370771136634908603L} + } + }; + + // second derivative for \beta = pi / 3 + long double tab13_2[Num][2*Num+1][2*Num+1] = { + { + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , 0.00000000000000000000000000000000L, NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } + },{ + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , -0.25000000000000000000000000000000L, 0.61237243569579452454932101867647L, 0.25000000000000000000000000000000L, NAN , NAN , NAN }, + { NAN , NAN , NAN , -0.61237243569579452454932101867647L, -0.50000000000000000000000000000000L, 0.61237243569579452454932101867647L, NAN , NAN , NAN }, + { NAN , NAN , NAN , 0.25000000000000000000000000000000L, -0.61237243569579452454932101867647L, -0.25000000000000000000000000000000L, NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } + },{ + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , 0.00000000000000000000000000000000L, 1.29903810567665797014558475612940L, -0.61237243569579452454932101867647L, -0.43301270189221932338186158537647L, 0.50000000000000000000000000000000L, NAN , NAN }, + { NAN , NAN , -1.29903810567665797014558475612940L, 0.75000000000000000000000000000000L, 2.12132034355964257320253308631450L, -1.25000000000000000000000000000000L, -0.43301270189221932338186158537647L, NAN , NAN }, + { NAN , NAN , -0.61237243569579452454932101867647L, -2.12132034355964257320253308631450L, 1.50000000000000000000000000000000L, 2.12132034355964257320253308631450L, -0.61237243569579452454932101867647L, NAN , NAN }, + { NAN , NAN , 0.43301270189221932338186158537647L, -1.25000000000000000000000000000000L, -2.12132034355964257320253308631450L, 0.75000000000000000000000000000000L, 1.29903810567665797014558475612940L, NAN , NAN }, + { NAN , NAN , 0.50000000000000000000000000000000L, 0.43301270189221932338186158537647L, -0.61237243569579452454932101867647L, -1.29903810567665797014558475612940L, 0.00000000000000000000000000000000L, NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } + },{ + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , 0.42187500000000000000000000000000L, 1.39211647546101543866416233789390L, -1.63391484918125399843500259053320L, 0.36309218870694533298555613122960L, 0.66566901262939977714018624058760L, -0.72920386809862713453837074842063L, 0.32812500000000000000000000000000L, NAN }, + { NAN , -1.39211647546101543866416233789390L, 2.53125000000000000000000000000000L, 1.28372474415273307841477292843940L, -3.25210268518692379865075808537980L, 1.45488804337309748887007598556460L, 0.53125000000000000000000000000000L, -0.72920386809862713453837074842063L, NAN }, + { NAN , -1.63391484918125399843500259053320L, -1.28372474415273307841477292843940L, 4.82812500000000000000000000000000L, 0.09375000000000000000000000000000L, -3.57812500000000000000000000000000L, 1.45488804337309748887007598556460L, 0.66566901262939977714018624058760L, NAN }, + { NAN , -0.36309218870694533298555613122960L, -3.25210268518692379865075808537980L, -0.09375000000000000000000000000000L, 5.43750000000000000000000000000000L, 0.09375000000000000000000000000000L, -3.25210268518692379865075808537980L, 0.36309218870694533298555613122960L, NAN }, + { NAN , 0.66566901262939977714018624058760L, -1.45488804337309748887007598556460L, -3.57812500000000000000000000000000L, -0.09375000000000000000000000000000L, 4.82812500000000000000000000000000L, 1.28372474415273307841477292843940L, -1.63391484918125399843500259053320L, NAN }, + { NAN , 0.72920386809862713453837074842063L, 0.53125000000000000000000000000000L, -1.45488804337309748887007598556460L, -3.25210268518692379865075808537980L, -1.28372474415273307841477292843940L, 2.53125000000000000000000000000000L, 1.39211647546101543866416233789390L, NAN }, + { NAN , 0.32812500000000000000000000000000L, 0.72920386809862713453837074842063L, 0.66566901262939977714018624058760L, -0.36309218870694533298555613122960L, -1.63391484918125399843500259053320L, -1.39211647546101543866416233789390L, 0.42187500000000000000000000000000L, NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } + },{ + { 0.84375000000000000000000000000000L, 1.03337848523665326017697921901650L, -2.23235266871074831073573829213310L, 1.51892360118934224163264861783310L, 0.00000000000000000000000000000000L, -0.91135416071360534497958917069987L, 0.90947701317845301548493041531350L, -0.49755260400283305119632332767463L, 0.15625000000000000000000000000000L}, + {-1.03337848523665326017697921901650L, 3.79687500000000000000000000000000L, -0.91135416071360534497958917069987L, -3.34852900306612246610360743819970L, 3.84260653723484939370788950519540L, -1.36421551976767952322739562297020L, -0.70883101388835971276190268832212L, 1.04687500000000000000000000000000L, -0.49755260400283305119632332767463L}, + {-2.23235266871074831073573829213310L, 0.91135416071360534497958917069987L, 5.90625000000000000000000000000000L, -4.47797343602549746076690994907170L, -2.37170824512628449899917015832450L, 4.70761309941142040747290533107540L, -1.90625000000000000000000000000000L, -0.70883101388835971276190268832212L, 0.90947701317845301548493041531350L}, + {-1.51892360118934224163264861783310L, -3.34852900306612246610360743819970L, 4.47797343602549746076690994907170L, 5.45312500000000000000000000000000L, -6.29359793758705243841630627464640L, -1.79687500000000000000000000000000L, 4.70761309941142040747290533107540L, -1.36421551976767952322739562297020L, -0.91135416071360534497958917069987L}, + { 0.00000000000000000000000000000000L, -3.84260653723484939370788950519540L, -2.37170824512628449899917015832450L, 6.29359793758705243841630627464640L, 5.00000000000000000000000000000000L, -6.29359793758705243841630627464640L, -2.37170824512628449899917015832450L, 3.84260653723484939370788950519540L, 0.00000000000000000000000000000000L}, + { 0.91135416071360534497958917069987L, -1.36421551976767952322739562297020L, -4.70761309941142040747290533107540L, -1.79687500000000000000000000000000L, 6.29359793758705243841630627464640L, 5.45312500000000000000000000000000L, -4.47797343602549746076690994907170L, -3.34852900306612246610360743819970L, 1.51892360118934224163264861783310L}, + { 0.90947701317845301548493041531350L, 0.70883101388835971276190268832212L, -1.90625000000000000000000000000000L, -4.70761309941142040747290533107540L, -2.37170824512628449899917015832450L, 4.47797343602549746076690994907170L, 5.90625000000000000000000000000000L, -0.91135416071360534497958917069987L, -2.23235266871074831073573829213310L}, + { 0.49755260400283305119632332767463L, 1.04687500000000000000000000000000L, 0.70883101388835971276190268832212L, -1.36421551976767952322739562297020L, -3.84260653723484939370788950519540L, -3.34852900306612246610360743819970L, 0.91135416071360534497958917069987L, 3.79687500000000000000000000000000L, 1.03337848523665326017697921901650L}, + { 0.15625000000000000000000000000000L, 0.49755260400283305119632332767463L, 0.90947701317845301548493041531350L, 0.91135416071360534497958917069987L, 0.00000000000000000000000000000000L, -1.51892360118934224163264861783310L, -2.23235266871074831073573829213310L, -1.03337848523665326017697921901650L, 0.84375000000000000000000000000000L} + } + }; + + // second derivative for \beta = 2 * pi / 3 + long double tab23_2[Num][2*Num+1][2*Num+1] = { + { + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , 0.00000000000000000000000000000000L, NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } + },{ + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , 0.25000000000000000000000000000000L, 0.61237243569579452454932101867647L, -0.25000000000000000000000000000000L, NAN , NAN , NAN }, + { NAN , NAN , NAN , -0.61237243569579452454932101867647L, 0.50000000000000000000000000000000L, 0.61237243569579452454932101867647L, NAN , NAN , NAN }, + { NAN , NAN , NAN , -0.25000000000000000000000000000000L, -0.61237243569579452454932101867647L, 0.25000000000000000000000000000000L, NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } + },{ + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , 0.50000000000000000000000000000000L, -0.43301270189221932338186158537647L, -0.61237243569579452454932101867647L, 1.29903810567665797014558475612940L, 0.00000000000000000000000000000000L, NAN , NAN }, + { NAN , NAN , 0.43301270189221932338186158537647L, 1.25000000000000000000000000000000L, -2.12132034355964257320253308631450L, -0.75000000000000000000000000000000L, 1.29903810567665797014558475612940L, NAN , NAN }, + { NAN , NAN , -0.61237243569579452454932101867647L, 2.12132034355964257320253308631450L, 1.50000000000000000000000000000000L, -2.12132034355964257320253308631450L, -0.61237243569579452454932101867647L, NAN , NAN }, + { NAN , NAN , -1.29903810567665797014558475612940L, -0.75000000000000000000000000000000L, 2.12132034355964257320253308631450L, 1.25000000000000000000000000000000L, -0.43301270189221932338186158537647L, NAN , NAN }, + { NAN , NAN , 0.00000000000000000000000000000000L, -1.29903810567665797014558475612940L, -0.61237243569579452454932101867647L, 0.43301270189221932338186158537647L, 0.50000000000000000000000000000000L, NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } + },{ + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN }, + { NAN , 0.32812500000000000000000000000000L, -0.72920386809862713453837074842063L, 0.66566901262939977714018624058760L, 0.36309218870694533298555613122960L, -1.63391484918125399843500259053320L, 1.39211647546101543866416233789390L, 0.42187500000000000000000000000000L, NAN }, + { NAN , 0.72920386809862713453837074842063L, -0.53125000000000000000000000000000L, -1.45488804337309748887007598556460L, 3.25210268518692379865075808537980L, -1.28372474415273307841477292843940L, -2.53125000000000000000000000000000L, 1.39211647546101543866416233789390L, NAN }, + { NAN , 0.66566901262939977714018624058760L, 1.45488804337309748887007598556460L, -3.57812500000000000000000000000000L, 0.09375000000000000000000000000000L, 4.82812500000000000000000000000000L, -1.28372474415273307841477292843940L, -1.63391484918125399843500259053320L, NAN }, + { NAN , -0.36309218870694533298555613122960L, 3.25210268518692379865075808537980L, -0.09375000000000000000000000000000L, -5.43750000000000000000000000000000L, 0.09375000000000000000000000000000L, 3.25210268518692379865075808537980L, 0.36309218870694533298555613122960L, NAN }, + { NAN , -1.63391484918125399843500259053320L, 1.28372474415273307841477292843940L, 4.82812500000000000000000000000000L, -0.09375000000000000000000000000000L, -3.57812500000000000000000000000000L, -1.45488804337309748887007598556460L, 0.66566901262939977714018624058760L, NAN }, + { NAN , -1.39211647546101543866416233789390L, -2.53125000000000000000000000000000L, 1.28372474415273307841477292843940L, 3.25210268518692379865075808537980L, 1.45488804337309748887007598556460L, -0.53125000000000000000000000000000L, -0.72920386809862713453837074842063L, NAN }, + { NAN , 0.42187500000000000000000000000000L, -1.39211647546101543866416233789390L, -1.63391484918125399843500259053320L, -0.36309218870694533298555613122960L, 0.66566901262939977714018624058760L, 0.72920386809862713453837074842063L, 0.32812500000000000000000000000000L, NAN }, + { NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN , NAN } + },{ + { 0.15625000000000000000000000000000L, -0.49755260400283305119632332767463L, 0.90947701317845301548493041531350L, -0.91135416071360534497958917069987L, 0.00000000000000000000000000000000L, 1.51892360118934224163264861783310L, -2.23235266871074831073573829213310L, 1.03337848523665326017697921901650L, 0.84375000000000000000000000000000L}, + { 0.49755260400283305119632332767463L, -1.04687500000000000000000000000000L, 0.70883101388835971276190268832212L, 1.36421551976767952322739562297020L, -3.84260653723484939370788950519540L, 3.34852900306612246610360743819970L, 0.91135416071360534497958917069987L, -3.79687500000000000000000000000000L, 1.03337848523665326017697921901650L}, + { 0.90947701317845301548493041531350L, -0.70883101388835971276190268832212L, -1.90625000000000000000000000000000L, 4.70761309941142040747290533107540L, -2.37170824512628449899917015832450L, -4.47797343602549746076690994907170L, 5.90625000000000000000000000000000L, 0.91135416071360534497958917069987L, -2.23235266871074831073573829213310L}, + { 0.91135416071360534497958917069987L, 1.36421551976767952322739562297020L, -4.70761309941142040747290533107540L, 1.79687500000000000000000000000000L, 6.29359793758705243841630627464640L, -5.45312500000000000000000000000000L, -4.47797343602549746076690994907170L, 3.34852900306612246610360743819970L, 1.51892360118934224163264861783310L}, + { 0.00000000000000000000000000000000L, 3.84260653723484939370788950519540L, -2.37170824512628449899917015832450L, -6.29359793758705243841630627464640L, 5.00000000000000000000000000000000L, 6.29359793758705243841630627464640L, -2.37170824512628449899917015832450L, -3.84260653723484939370788950519540L, 0.00000000000000000000000000000000L}, + {-1.51892360118934224163264861783310L, 3.34852900306612246610360743819970L, 4.47797343602549746076690994907170L, -5.45312500000000000000000000000000L, -6.29359793758705243841630627464640L, 1.79687500000000000000000000000000L, 4.70761309941142040747290533107540L, 1.36421551976767952322739562297020L, -0.91135416071360534497958917069987L}, + {-2.23235266871074831073573829213310L, -0.91135416071360534497958917069987L, 5.90625000000000000000000000000000L, 4.47797343602549746076690994907170L, -2.37170824512628449899917015832450L, -4.70761309941142040747290533107540L, -1.90625000000000000000000000000000L, 0.70883101388835971276190268832212L, 0.90947701317845301548493041531350L}, + {-1.03337848523665326017697921901650L, -3.79687500000000000000000000000000L, -0.91135416071360534497958917069987L, 3.34852900306612246610360743819970L, 3.84260653723484939370788950519540L, 1.36421551976767952322739562297020L, -0.70883101388835971276190268832212L, -1.04687500000000000000000000000000L, -0.49755260400283305119632332767463L}, + { 0.84375000000000000000000000000000L, -1.03337848523665326017697921901650L, -2.23235266871074831073573829213310L, -1.51892360118934224163264861783310L, 0.00000000000000000000000000000000L, 0.91135416071360534497958917069987L, 0.90947701317845301548493041531350L, 0.49755260400283305119632332767463L, 0.15625000000000000000000000000000L} + } + }; + + //check derivative functions against table + const Real eps = std::numeric_limits::epsilon() * 24; + for(int j = 0; j < Num; j++) { + for(int k = 1-Num; k < Num; k++) { + const size_t ik = k + Num-1;// (-Num,Num) ==> [0L,2*Num+1) + const size_t nk = Num-1 - k; + for(int m = 1-Num; m < Num; m++) { + const size_t im = m + Num-1;// (-Num,Num) ==> [0L,2*Num+1) + + //compute values + const Real dVals[8] = { + dPrime (j, k, m, 0.5L, false),// pi/3 + dPrime (j, k, m,-0.5L, false),// 2*pi/3 + dPrime2(j, k, m, 0.5L, false),// pi/3 + dPrime2(j, k, m,-0.5L, false),// 2*pi/3 + + dPrime (j, k, m, 0.5L, true ),// - pi/3 + dPrime (j, k, m,-0.5L, true ),// -2*pi/3 + dPrime2(j, k, m, 0.5L, true ),// - pi/3 + dPrime2(j, k, m,-0.5L, true ),// -2*pi/3 + }; + + //pull values from table + const Real neg = Real(0 == (std::abs(m) + std::abs(k) + 1) % 2 ? 1 : -1); + const Real tVals[8] = { + static_cast(tab13_1[j][ik][im]) , + static_cast(tab23_1[j][ik][im]) , + static_cast(tab13_2[j][ik][im]) , + static_cast(tab23_2[j][ik][im]) , + static_cast(tab13_1[j][ik][im]) * neg,//d^j_{ k, m}(-beta) = d^j_{m,k}(beta) + static_cast(tab23_1[j][ik][im]) * neg,//d^j_{ k, m}(-beta) = d^j_{m,k}(beta) + static_cast(tab13_2[j][ik][im]) * -neg, + static_cast(tab23_2[j][ik][im]) * -neg, + }; + + //compare values + for(size_t i = 0; i < 8; i++) { + if(std::isnan(dVals[i]) && std::isnan(tVals[i])) continue;//both are undefined + const double delta = std::fabs(dVals[i] - tVals[i]);//compute difference + if(delta > eps) { + std::string b;//beta string + switch(i) { + case 0: //intentional fall through + case 2: b = "Pi/3"; break; + case 1: //intentional fall through + case 3: b = "2*Pi/3"; break; + case 4: //intentional fall through + case 6: b = "-Pi/3"; break; + case 5: //intentional fall through + case 7: b = "-2*Pi/3"; break; + } + + char d = '1';//derivative string + switch(i) { + case 2: + case 3: + case 6: + case 7: d = '2'; break; + } + + os << "mismatch for d^j_{k,m} derivative with D[WignerD[{" << j << ", " << k << ", " << m << "}, " << b << "], {\\beta, " << d << "}]:\n"; + os << std::setprecision(std::numeric_limits::digits10); + os << "\ttable: " << tVals[i] << '\n'; + os << "\tfunc" << i << ": " << dVals[i] << '\n'; + os << "\tdelta: " << delta << '\n'; + os << "\tlimit: " << eps << '\n'; + os.flush(); + return false; + } + } + } + } + } + + //if we made it this far all the tests passed + return true; + + } + + //@brief : run unit tests for all function in this header + //@param os: output stream to write error messages to + //@return : true/false if the self tests pass/fail + //@note : there isn't currently a unit test for rotateHarmonics + template bool runTests(std::ostream& os) { + return testDjkm(os) && testTables(15L, os) && testDerivatives(os); + } + } + +} diff --git a/test/util/base64.cpp b/test/util/base64.cpp new file mode 100644 index 0000000..2dbaa36 --- /dev/null +++ b/test/util/base64.cpp @@ -0,0 +1,143 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +//@brief : check if base64 encode/decode is self consistent +//@return: true / false if tests pass / fail +bool testBase64(std::ostream& os); + +int main() { + try { + return testBase64(std::cout) ? EXIT_SUCCESS : EXIT_FAILURE; + } catch(std::exception& e) { + std::cout << "caught: " << e.what(); + return EXIT_FAILURE; + } +} + +#include "util/base64.hpp" + +#include +#include +#include +#include + +//@brief : check if base64 encode/decode is self consistent +//@return: true / false if tests pass / fail +bool testBase64(std::ostream& os) { + //make sure a few different strings have the expected encoding + std::string test[3] = { + "test string length % 3 == 0" , + "test string length % 3 == 1 ", + "test string length % 3 == 2 " , + }; + + //expected encodings + std::string enc[3] = { + "dGVzdCBzdHJpbmcgbGVuZ3RoICUgMyA9PSAw" , + "dGVzdCBzdHJpbmcgbGVuZ3RoICUgMyA9PSAxICA=", + "dGVzdCBzdHJpbmcgbGVuZ3RoICUgMyA9PSAyIA==", + }; + + //loop over test strings + for(size_t i = 0; i < 3; i++) { + //make sure encoding matches expected data + std::stringstream ss; + size_t len = base64::encode(test[i].data(), test[i].size(), ss);//encode + if(0 != enc[i].compare(ss.str())) {//make sure encoding matches our expected value + os << "expected `" << test[i] << "' to encode as:\n\t`" << enc[i] << "'\nbut got\n\t`" << ss.str() << "'\n"; + return false; + } + if(len != ss.str().size()) {//make sure the returned size is correct + os << "encoded length is " << ss.str().size() << " but encoding returned " << len << '\n'; + return false; + } + + //make sure decoding matches expected data + ss.str(std::string()); + len = base64::decode(enc[i].data(), enc[i].size(), ss);//decode + if(0 != test[i].compare(ss.str())) {//make sure encoding matches our expected value + os << "expected `" << enc[i] << "' to decode as:\n\t`" << test[i] << "'\nbut got\n\t`" << ss.str() << "'\n"; + return false; + } + if(len != ss.str().size()) {//make sure the returned size is correct + os << "encoded length is " << ss.str().size() << " but encoding returned " << len << '\n'; + return false; + } + } + + //now test for round trip self consistency on random data + + //generate some random data + char buff[64]; + std::default_random_engine gen; + std::uniform_int_distribution dist(std::numeric_limits::min(), std::numeric_limits::max()); + for(size_t i = 0; i < sizeof(buff); i++) buff[i] = (char)dist(gen); + + //loop over all termination types (3*n+ 0,1, or 2) + for(size_t i = 0; i < 3; i++) { + //encode the data + const size_t bytes = sizeof(buff) - i; + std::stringstream ssEnc, ssDec; + size_t len = base64::encode(buff, bytes, ssEnc); + if(len != ssEnc.str().size()) { + os << "encoded length is " << ssEnc.str().size() << " but encoding returned " << len << '\n'; + return false; + } + + //decode the data + len = base64::decode(ssEnc.str().data(), ssEnc.str().size(), ssDec); + if(len != ssDec.str().size()) { + os << "encoded length is " << ssDec.str().size() << " but encoding returned " << len << '\n'; + return false; + } + + //sanity check output vs input length + if(len != bytes) { + os << "round trip decoding of " << bytes << " bytes became " << len << " bytes\n"; + return false; + } + + //make sure we got back what we started with + if(!std::equal(buff, buff + len, ssDec.str().c_str())) { + os << "round trip encode/decode failed for " << bytes << " bytes encoded as:\n\t`" << ssEnc.str() << "'\n"; + return false; + } + } + + //if we made it this far all tests passed + os << "all tests passed\n"; + return true; +} diff --git a/test/util/colorspace.cpp b/test/util/colorspace.cpp new file mode 100644 index 0000000..c07e723 --- /dev/null +++ b/test/util/colorspace.cpp @@ -0,0 +1,313 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019, William C. Lenthe * + * All rights reserved. * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace color { + //@brief : test color space conversions for 2 and 3 way self consistency, e.g. for 2 way make sure rgb2hsv, hvs2rgb gives back the input + //@param n : RGB grid density + //@param do3: should 3 way tests be included (this can significantly increase computation time) + //@param os : location to write errors + //@return : true / false if tests pass / fail + template bool testRound(const size_t n, const bool do3, std::ostream& os); + + //@brief : run all color space tests (2 and 3 way for different grid sizes) + //@return: true / false if tests pass / fail + template bool testRots(std::ostream& os); +} + +int main() { + std::ostream& os = std::cout; + return color::testRots(os) && color::testRots(os) ? EXIT_SUCCESS : EXIT_FAILURE; +} + +#include +#include +#include +#include +#include //transform +#include //iota + +#include "util/colorspace.hpp" + +namespace color { + + //@brief : compute maximum of the element wise distance between two 3d vectors + //@param a : first vector + //@param b : second vector + //@return : L1 distance + template Real maxDelta(Real const * const a, Real const * const b) { + Real maxDist = 0; + for(size_t i = 0; i < 3; i++) { + const Real dist = std::fabs(a[i] - b[i]); + if(dist > maxDist) maxDist = dist; + } + return maxDist; + } + + //@brief : special comparison for hsl + //@param a : first hsl + //@param b : second hsl + //@return : L1 distance + template Real compHsl(Real const * const a, Real const * const b) { + //compute max chroma + const Real cA = std::min(a[2], Real(1) - a[2]); + const Real cB = std::min(b[2], Real(1) - b[2]); + + //start by comparing hue periodically + Real maxDist = std::fabs(a[0] - b[0]); + maxDist = std::min(maxDist, Real(1) - maxDist); + maxDist *= std::max(a[1], b[1]) * std::max(cA, cB);//normalize by max saturation and chroma + + //now compare chroma normalized saturation + maxDist = std::max(maxDist, std::fabs(a[1]*cA - b[1]*cB)); + + //finally compare lightness + return std::max(maxDist, std::fabs(a[2] - b[2])); + } + + //@brief : special comparison for hsv + //@param a : first hsv + //@param b : second hsv + //@return : L1 distance + template Real compHsv(Real const * const a, Real const * const b) { + //compute max chroma + const Real cA = a[2]; + const Real cB = b[2]; + + //start by comparing hue periodically + Real maxDist = std::fabs(a[0] - b[0]); + maxDist = std::min(maxDist, Real(1) - maxDist); + maxDist *= std::max(a[1], b[1]);//normalize by max saturation + + //now compare chroma normalized saturation + maxDist = std::max(maxDist, std::fabs(a[1]*cA - b[1]*cB)); + + //finally compare value + return std::max(maxDist, std::fabs(a[2] - b[2])); + } + + //@brief : test color space conversions for 2 and 3 way self consistency, e.g. for 2 way make sure rgb2hsv, hvs2rgb gives back the input + //@param num: RGB grid density + //@param do3: should 3 way tests be included (this can significantly increase computation time) + //@param os : location to write errors + //@return : true / false if tests pass / fail + template bool testRound(const size_t num, const bool do3, std::ostream& os) { + std::string names[6] = {"rgb", "xyz", "luv", "lab", "hsv", "hsl"}; + + //build illumination dependant pointers + typedef std::function conversionFunc;//signature for conversion function pointer + Real const * ill = NULL;//use default illuminant + conversionFunc rgbXluv = std::bind(rgb2luv, std::placeholders::_1, std::placeholders::_2, ill); + conversionFunc rgbXlab = std::bind(rgb2lab, std::placeholders::_1, std::placeholders::_2, ill); + conversionFunc xyzXluv = std::bind(xyz2luv, std::placeholders::_1, std::placeholders::_2, ill); + conversionFunc xyzXlab = std::bind(xyz2lab, std::placeholders::_1, std::placeholders::_2, ill); + conversionFunc luvXrgb = std::bind(luv2rgb, std::placeholders::_1, std::placeholders::_2, ill); + conversionFunc luvXxyz = std::bind(luv2xyz, std::placeholders::_1, std::placeholders::_2, ill); + conversionFunc luvXlab = std::bind(luv2lab, std::placeholders::_1, std::placeholders::_2, ill); + conversionFunc luvXhsv = std::bind(luv2hsv, std::placeholders::_1, std::placeholders::_2, ill); + conversionFunc luvXhsl = std::bind(luv2hsl, std::placeholders::_1, std::placeholders::_2, ill); + conversionFunc labXrgb = std::bind(lab2rgb, std::placeholders::_1, std::placeholders::_2, ill); + conversionFunc labXxyz = std::bind(lab2xyz, std::placeholders::_1, std::placeholders::_2, ill); + conversionFunc labXluv = std::bind(lab2luv, std::placeholders::_1, std::placeholders::_2, ill); + conversionFunc labXhsv = std::bind(lab2hsv, std::placeholders::_1, std::placeholders::_2, ill); + conversionFunc labXhsl = std::bind(lab2hsl, std::placeholders::_1, std::placeholders::_2, ill); + conversionFunc hsvXluv = std::bind(hsv2luv, std::placeholders::_1, std::placeholders::_2, ill); + conversionFunc hsvXlab = std::bind(hsv2lab, std::placeholders::_1, std::placeholders::_2, ill); + conversionFunc hslXluv = std::bind(hsl2luv, std::placeholders::_1, std::placeholders::_2, ill); + conversionFunc hslXlab = std::bind(hsl2lab, std::placeholders::_1, std::placeholders::_2, ill); + + //build a table of conversion functions + conversionFunc conversion[6][6] = { + // 2rgb 2xyz 2luv 2lab 2hsv 2hsl + { NULL, &rgb2xyz, rgbXluv , rgbXlab , &rgb2hsv, &rgb2hsl},// rgb2 + {&xyz2rgb, NULL, xyzXluv , xyzXlab , &xyz2hsv, &xyz2hsl},// xyz2 + { luvXrgb , luvXxyz , NULL, luvXlab , luvXhsv , luvXhsl },// luv2 + { labXrgb , labXxyz , labXluv , NULL, labXhsv , labXhsl },// lab2 + {&hsv2rgb, &hsv2xyz, hsvXluv , hsvXlab , NULL, &hsv2hsl},// hsv2 + {&hsl2rgb, &hsl2xyz, hslXluv , hslXlab , &hsl2hsv, NULL},// hsl2 + }; + + //build a table of comparison functions + typedef Real (*comparisonFunc)(Real const * const, Real const * const);//signature for comparison function pointer + comparisonFunc comparison[6] = { + &maxDelta, + &maxDelta, + &maxDelta, + &maxDelta, + &compHsv , + &compHsl , + }; + + //build a grid of RGB values + std::vector lin(num);//[0,1] + std::iota(lin.begin(), lin.end(), Real(0)); + std::for_each(lin.begin(), lin.end(), [num](Real& v){v /= (num-1);}); + + //2 way tests + Real maxDiff = Real(0); + size_t maxI = 0, maxJ = 0, maxK = 0, maxM = 0, maxN = 0, maxP = 0; + Real rgb[3], x[3], y[3], z[3], final[3]; + for(size_t i = 0; i < 6; i++) { + for(size_t j = 0; j < 6; j++) { + if(i == j) continue; + for(size_t m = 0; m < num; m++) { + rgb[0] = lin[m]; + for(size_t n = 0; n < num; n++) { + rgb[1] = lin[n]; + for(size_t p = 0; p < num; p++) { + rgb[2] = lin[p]; + + //get base orientation from eulers + if(i == 0) std::copy(rgb, rgb+3, x); + else conversion[0][i](rgb, x); + + //do conversion + conversion[i][j](x, y);//x2y + conversion[j][i](y, final);//y2x + + //compute error + Real diff = comparison[i](x, final); + if(diff > maxDiff) { + maxDiff = diff; + maxI = i; + maxJ = j; + maxM = m; + maxN = n; + maxP = p; + } + } + } + } + } + } + + os << "max diff pairwise = " << names[maxI] << "2" << names[maxJ] << "-" << names[maxJ] << "2" << names[maxI] << ": " << maxDiff << "\n"; + + //select threshold to pass, I choose cube root due to trig operators + //cbrt(epsilon) is ~0.005 and 6e-6 for float/double respectively + const Real eps2 = std::cbrt(std::numeric_limits::epsilon()); + if(maxDiff > eps2) { + os << "outside limit (" << eps2 << ")\n"; + + //get worst euler angle + rgb[0] = lin[maxM]; + rgb[1] = lin[maxN]; + rgb[2] = lin[maxP]; + + //convert to base orientation + if(maxI == 0) std::copy(rgb, rgb+3, x); + else conversion[0][maxI](rgb, x); + + //do conversion + conversion[maxI][maxJ](x, y);//x2y + conversion[maxJ][maxI](y, final);//y2x + + os << "\trgb = (" << rgb [0] << '\t' << rgb [1] << '\t' << rgb [2] << ")\n"; + os << "\t" << names[maxI] << " = (" << x [0] << '\t' << x [1] << '\t' << x [2] << ")\n"; + os << "\t" << names[maxJ] << " = (" << y [0] << '\t' << y [1] << '\t' << y [2] << ")\n"; + os << "\t" << names[maxI] << " = (" << final[0] << '\t' << final[1] << '\t' << final[2] << ")\n"; + os << comparison[maxI](x, final) << '\n'; + return false; + } + + if(do3) { + //3 way tests + maxDiff = Real(0); + maxI = maxJ = maxK = maxM = maxN = maxP = 0; + for(size_t i = 0; i < 6; i++) { + for(size_t j = 0; j < 6; j++) { + if(i == j) continue; + for(size_t k = 0; k < 6; k++) { + if(j == k || k == i) continue; + for(size_t m = 0; m < num; m++) { + rgb[0] = lin[m]; + for(size_t n = 0; n < num; n++) { + rgb[1] = lin[n]; + for(size_t p = 0; p < num; p++) { + rgb[2] = lin[p]; + //get base orientation from rgblers + if(i == 0) std::copy(rgb, rgb+3, x); + else conversion[0][i](rgb, x); + + //do conversion + conversion[i][j](x, y);//x2y + conversion[j][k](y, z);//y2z + conversion[k][i](z, final);//z2x + + //compute error + Real diff = comparison[i](x, final); + if(diff > maxDiff) { + maxDiff = diff; + maxI = i; + maxJ = j; + maxK = k; + maxM = m; + maxN = n; + maxP = p; + } + } + } + } + } + } + } + + os << "max diff three way = " << names[maxI] << "2" << names[maxK] << "-" << names[maxK] << "2" << names[maxJ] << "-" << names[maxJ] << "2" << names[maxI] << ": " << maxDiff << "\n"; + //select threshold to pass, I choose cube root due to trig operators + //cbrt(epsilon) is ~0.005 and 6e-6 for float/double respectively + const Real eps3 = std::cbrt(std::numeric_limits::epsilon()); + if(maxDiff > eps3) { + os << "outside limit (" << eps3 << ")\n"; + + //get worst euler angle + rgb[0] = lin[maxM]; + rgb[1] = lin[maxN]; + rgb[2] = lin[maxP]; + + //convert to base orientation + if(maxI == 0) std::copy(rgb, rgb+3, x); + else conversion[0][maxI](rgb, x); + + //do conversion + conversion[maxI][maxJ](x, y);//x2y + conversion[maxJ][maxK](y, z);//y2z + conversion[maxK][maxI](z, final);//z2x + + os << "\trgb = (" << rgb [0] << '\t' << rgb [1] << '\t' << rgb [2] << ")\n"; + os << "\t" << names[maxI] << " = (" << x [0] << '\t' << x [1] << '\t' << x [2] << ")\n"; + os << "\t" << names[maxJ] << " = (" << y [0] << '\t' << y [1] << '\t' << y [2] << ")\n"; + os << "\t" << names[maxK] << " = (" << z [0] << '\t' << z [1] << '\t' << z [2] << ")\n"; + os << "\t" << names[maxI] << " = (" << final[0] << '\t' << final[1] << '\t' << final[2] << ")\n"; + return false; + } + } + //if we made it this far everything passed + return true; + } + + //@brief : run all rotation tests + //@return: true / false if tests pass / fail + template bool testRots(std::ostream& os) { + return testRound(51, false, os) && //high grid density for 2 way + testRound(31, true , os); //lower grid density for 3 way + } +} diff --git a/test/util/linalg.cpp b/test/util/linalg.cpp new file mode 100644 index 0000000..c1b549a --- /dev/null +++ b/test/util/linalg.cpp @@ -0,0 +1,693 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include + +//@brief : test real valued linalg functions +//@return: true / false if tests pass / fail +template bool testLinAlg(std::ostream& os); + +int main() { + std::ostream& os = std::cout; + try { + os << "testing float \n"; + if(!testLinAlg< float >(os)) return EXIT_FAILURE; + os << "testing double \n"; + if(!testLinAlg< double >(os)) return EXIT_FAILURE; + /*this doesn't currently pass (and isn't needed for EMSphInx) + os << "testing complex\n"; + if(!testLinAlg< std::complex >(os)) return EXIT_FAILURE; + os << "testing complex\n"; + if(!testLinAlg< std::complex >(os)) return EXIT_FAILURE; + */ + } catch (std::exception& e) { + os << "caught: " << e.what() << '\n'; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +#include "util/linalg.hpp" + +//short cut since we'll be using this alot +template using ReVal = typename detail::ReVal::type; + +#include +#include +#include +#include +#include +#include +#include + +//@brief : helper to build a random real/complex number +//@param mag: magnitude +//@param gen: random generator +//@return : mag +template = 0> +Real rndPhs(const ReVal& mag, URNG& gen) {return mag;} + +//@brief : helper to build a random real/complex number +//@param mag: magnitude +//@param gen: random generator +//@return : mag with random phase +template = 0> +Cplx rndPhs(const ReVal& mag, URNG& gen) { + static std::uniform_real_distribution dis(0, (ReVal)6.2831853071795864769252867665590); + return std::polar(mag, dis(gen)); +} + +//lets make life a little easier by defining a bare bones matrix class +//this isn't super efficient (but it doesn't need to be) +template class Mat { + size_t _m, _n;//rows, cols + std::vector _a;//data in row major order + + public: + //@brief : construct an empty matrix (all zeros) + //@param r: number of rows + //@param c: number of columns + Mat(const size_t r, const size_t c) : _m(r), _n(c), _a(r*c, Real(0)) {} + + //@brief: construct an empty matrix + Mat() : Mat(0, 0) {} + + //@brief : construct the identity matrix + //@param n: side length + static Mat Ident(const size_t n) {Mat a(n, n); for(size_t i = 0; i < n; i++) a[i][i] = Real(1); return a;} + + //@brief : get access to the rth row of data + //@param r: row to get + //@return : pointer to row start + Real const * operator[](const size_t r) const {return _a.data() + _n * r;} + Real * operator[](const size_t r) {return _a.data() + _n * r;} + + //@brief : get the # of rows/columns in this matrix + //@return: # of rows/columns + size_t rows() const {return _m;} + size_t cols() const {return _n;} + + //@brief: get underlying storage (row major order) + Real const * data() const {return _a.data();} + Real * data() {return _a.data();} + + //@brief : access to underlying storage iterators + //@return: iterators for row major order storage + typename std::vector::const_iterator cbegin() const {return _a.cbegin();} + typename std::vector::const_iterator cend () const {return _a.cend ();} + typename std::vector:: iterator begin() {return _a. begin();} + typename std::vector:: iterator end () {return _a. end ();} + + //@brief : resize a matrix, padding with 0 if needed + //@param r/c: new number of rows/columns + void resize (const size_t r, const size_t c) {setRows(r); setCols(c);} + void setRows(const size_t r) {_m = r; _a.resize(_m * _n, Real(0));} + void setCols(const size_t c) { + if(c < _n) {//shrinking + for(size_t i = 1; i < _m; i++) std::copy(_a.begin() + i * _n, _a.begin() + i * _n + c, _a.begin() + i * c);//loop over rows repacking + _a.resize(c * _m);//resize + } else if(c > _n) {//expanding + _a.resize(c * _m);//resize + for(size_t i = _m; i-- > 0 ; ) { + std::copy_backward(_a.begin() + i * _n, _a.begin() + i * _n + _n, _a.begin() + i * c + _n);//repack (this does nothing for i==0) + std::fill(_a.begin() + i * c + _n, _a.begin() + i * c + c, Real(0));//fill new space with zeros + } + } + _n = c; + } + + //@brief : multiply 2 matrices together + //@param a: other matrix to multiply by + //@return : this * a + Mat operator*(const Mat& a) const { + if(_n != a._m) throw std::runtime_error("matrix mutliplication size mismatch");//sanity check shapes + const size_t r = _m; + const size_t c = a._n; + const size_t p = _n; + Mat mat(r, c); + for(size_t i = 0; i < r; i++) {//loop over output rows + for(size_t j = 0; j < c; j++) {//loop over output columns + Real v(0);//initialize value + for(size_t k = 0; k < p; k++) v += _a[i*p+k] * a._a[k*c+j];//compute sum + mat._a[i*c+j] = v;//save value + } + } + return mat; + } + + //@brief : transpose this matrix + //@return: this^T + Mat transpose() const { + Mat res(_n, _m); + for(size_t i = 0; i < _n; i++) { + for(size_t j = 0; j < _m; j++) { + res[i][j] = operator[](j)[i]; + } + } + return res; + } + + //@brief : conjugate transpose this matrix + //@return: this^* + Mat conjugateTranspose() const { + Mat res(_n, _m); + for(size_t i = 0; i < _n; i++) { + for(size_t j = 0; j < _m; j++) { + res[i][j] = detail::conj(operator[](j)[i]); + } + } + return res; + } + + //@brief : compare this matrix to another + //@param a: matrix to compare to + //@return : L2-norm sqrt ( ||this - a||^2 ) if the a is the same shape as this, largest possible Real otherwise + ReVal compare(const Mat& a) { + if(a._m != _m || a._n != _n) return std::numeric_limits >::max();// matrices must have same size + ReVal res(0); + for(size_t i = 0; i < _a.size(); i++) res += std::norm(_a[i] - a._a[i]); + return std::sqrt(res); + } + + //@brief : convert to string representation + //@param os : ostream to print to + //@param sci: should value be printed with scientific (instead of fixed) notation + //@return : os + std::ostream& print(std::ostream& os, const bool sci = false) const { + os << (sci ? std::scientific : std::fixed) << std::setprecision(4); + for(int i = 0; i < _m; i++) { + for(int j = 0; j < _n; j++) { + os << std::setw(sci ? 13 : 10) << _a[i*_n+j]; + } + os << '\n'; + } + os << '\n'; + // os << std::defaultfloat; this isn't supported until fairly recent versions of gcc + os.unsetf(std::ios_base::floatfield); + return os; + } + + friend std::ostream& operator<<(std::ostream& os, const Mat& m) {return m.print(os, false);} + +}; + +//@brief : compare 2 vectors +//@param a: first vector to compare +//@param b: second vector to compare +//@return : |a-b| / min(|a|, |b|) +template +ReVal vectorCompare(const std::vector& a, const std::vector& b) { + if(a.size() != b.size()) return std::numeric_limits >::max();// vectors must have same length + ReVal ma(0), mb(0), mab(0); + for(size_t i = 0; i < a.size(); i++) { + ma += std::norm(a[i]); + mb += std::norm(b[i]); + mab += std::norm(a[i] - b[i]); + } + return std::sqrt(mab / std::max(ma, mb)); +} + +//////////////////////////////////////////////////////////////////////// +// Random Matrix Generation // +//////////////////////////////////////////////////////////////////////// + +//@brief : generate a random orthogonal matrix +//@param n : side length +//@param gen: random generator +//@return : nxn random matrix +//@reference: Stewart, G. W. (1980). The efficient generation of random orthogonal matrices with an application to condition estimators. SIAM Journal on Numerical Analysis, 17(3), 403-409. +template = 0> +Mat randomQ(const size_t n, URNG& gen) { + Mat a = Mat::Ident(n);//start with identity matrix + std::normal_distribution> dis(ReVal(0), ReVal(1));//we'll also need a random distribution + + //to produce a random orthogonal (n+1) x (n+1) matrix: + // -start with a random orthogonal n x n matrix + // -construct an (n+1)^2 matrix from the n^2 by augmenting with a 1 in the bottom right + // -generate a normal random (n+1) unit vector + // -apply a householder reflection to the (n+1)^2 matrix using the random vector + //to build our random matrix we start from a random 1x1 orthogonal matrix (i.e. [1]) and work up to n x n + std::vector x(n), row(n);//work space + for(size_t i = 1; i < n; i++) {//loop over size we want to augment to + //generate a random unit vector with normally distributed elements + Real mag2 = 0; + for(size_t j = 0; j <= i; j++) { + const Real r = rndPhs(dis(gen), gen);//draw a random number + mag2 += r * r;//accumulate magnitude + x[j] = r;//save value + } + + //apply house holder transform row by row such that a *= h + for(size_t r = 0; r <= i; r++) {//loop over rows of a + const Real vh = x[r] * ReVal(2) / mag2;//we'll need this value multiple times + std::fill(row.begin(), row.begin() + i+1, Real(0));//fill output row with 0 + for(size_t c = 0; c <= i; c++) {//loop over cols of a + Real& v = row[c];//get output value + for(size_t k = 0; k <= i; k++) {//loop down rows of h + const Real h = ReVal(c == k ? 1 : 0) - x[c] * x[k] * ReVal(2) / mag2;//compute value of householder matrix + v += h * a[r][k];//accumulate matrix product + } + } + std::copy(row.begin(), row.begin() + i+1, a[r]);//copy output back over a + } + } + return a; +} + +//@brief : generate a random unitary matrix +//@param n : side length +//@param gen: random generator +//@return : nxn random matrix +//@reference: Mezzadri, F. (2007). How to generate random matrices from the classical compact groups. arXiv [arXiv:math-ph/0609050] +template = 0> +Mat randomQ(const size_t n, URNG& gen) { + //generate a random matrix + Mat q = Mat::Ident(n);//start with identity matrix + Mat qr(q);//make a copy of the same size + std::normal_distribution> dis(ReVal(0), ReVal(1));//we'll also need a random distribution + for(Cplx& v : qr) v = rndPhs(dis(gen), gen);//fill with random normally distributed numbers + + //now do the QR decomposition and extract Q + decompose::qr(qr.data(), n, n); + qr::applyQ(qr.data(), q.data(), n, n, n); + + //let D == diag(R) / element wise abs(diag(R)) + //simultaneously extract D and compute D * Q + for(size_t j = 0; j < n; j++) { + const Cplx d = qr[j][j] / std::sqrt(std::norm(qr[j][j]));//compute d/|d| + for(size_t i = 0; i < n; i++) qr[j][i] = q[j][i] * d;//fill row with d * q + } + + // random Q == q * d * q + return q * qr; +} + +//@brief : generate a random square matrix with the specified eigen values and random eigen vectors +//@param d : eigen values +//@return : nxn random matrix +//@param gen: random generator +//@note : a very slight modification would enable random rectangular matrices from specified singular values +template +Mat randomEigVec(const std::vector d, URNG& gen) { + //handle trivial case + if(d.size() <= 1) { + Mat res = Mat::Ident(d.size()); + if(1 == d.size()) res[0][0] = d[0]; + return res; + } + + //generate random eigen vectors and assmble + Mat q = randomQ(d.size(), gen); + Mat qt = q.transpose(); + for(size_t r = 0; r < d.size(); r++) std::transform(d.begin(), d.end(), q[r], q[r], std::multiplies());//q *= d; + return q * qt; +} + +//@brief : generate random eigen values with the specified condition number and definite-ness +//@param n : number of eigen values +//@param c : condition number +//@param gen: random generator +//@param def: should the values be all real positive, all real negative, or mixed (and potentially complex) [+1, -1, or 0 respectively] +template +std::vector randomEigVal(const size_t n, const ReVal c, URNG& gen, const int def = 0) { + //handle trivial case + if(n < 2) return std::vector(n, 1); + + //start by sanity checking condition number and generating eigen value bounds + if(c < ReVal(1)) throw std::runtime_error("cannot generate matrix with condition number <1"); + const ReVal vMax = ReVal(1);// |largest eigen value| (this could be anything, but 1 is nice) + const ReVal vMin = vMax / c;// |smallest eigen value| + + //now build our distributions + static std::uniform_real_distribution> disSgn(ReVal(-1), ReVal(1));//random sign generation for def == 0 + std::uniform_real_distribution> dis(vMin, vMax);//magnitude generation + + //generate random eigen values by from the distribution + std::vector diag(n); + switch(def) { + case 0://random eigen values + for(Real& v : diag) v = rndPhs(std::copysign(dis(gen), disSgn(gen)), gen);//random value in range * random sign (w/ random phase if complex) + break; + + case 1: for(Real& v : diag) v = dis(gen); break;;// random value in range * random sign + case -1: for(Real& v : diag) v = -dis(gen); break;//-random value in range * random sign + } + + //make sure we achieve the desired condition number + switch(def) { + case 0: + diag[0] = rndPhs(std::copysign(vMin, disSgn(gen)), gen);//set upper magnitude bound + diag[1] = rndPhs(std::copysign(vMax, disSgn(gen)), gen);//set lower magnitude bound + break; + + case 1: + diag[0] = vMin; + diag[1] = vMax; + break; + + case -1: + diag[0] = -vMin; + diag[1] = -vMax; + break; + } + + //randomly shuffle our eigenvalues and return + std::shuffle(diag.begin(), diag.end(), gen); + return diag; +} + +//@brief : generate a random square matrix with the specified condition number +//@param n : side length +//@param c : condition number +//@param gen: random generator +//@return : nxn random matrix +template +Mat randomMatrix(const size_t n, const ReVal c, URNG& gen, const bool pd = false) {return randomEigVec(randomEigVal(n, c, gen, 0), gen);} + +//@brief : generate a random positive or negative definite square matrix with the specified condition number +//@param n : side length +//@param c : condition number +//@param pos: true/false for positive/negative definite +//@param gen: random generator +//@return : nxn random matrix +template +Mat randomDefinite(const size_t n, const ReVal c, URNG& gen, const bool pos = true) {return randomEigVec(randomEigVal(n, c, gen, pos ? 1 : -1), gen);} + +//////////////////////////////////////////////////////////////////////// +// Tests for Different Decompositions // +//////////////////////////////////////////////////////////////////////// + +//@brief : test LU decomposition for a random matrix matrix +//@param os : location to write error messages +//@param n : matrix size +//@param cn : condition number to test for +//@param gen: random generator e.g. std::mt19937 +//@return : true/false if test passed/failed +template +bool testLU(std::ostream& os, const size_t n, const ReVal cn, URNG& gen) { + //create a random matrix + Mat a = randomMatrix(n, cn, gen); + + //do in place decomposition on a copy + Mat lu(a);//copy to keep a + std::vector p(n);//allocate permutation matrix + decompose::lu(lu.data(), p.data(), n);//in place decompose + + //extract L and U + Mat l(lu), u(lu); + for(size_t r = 0; r < n; r++) { + for(size_t c = 0; c < r; c++) { + u[r][c] = Real(0); + } + l[r][r] = Real(1); + for(size_t c = r+1; c < n; c++) { + l[r][c] = Real(0); + } + } + + //compute p * a + Mat pa(n, n); + for(size_t i = 0; i < p.size(); i++) { + std::copy(a[p[i]], a[p[i]] + n, pa[i]); + } + + //make sure that L * U == P * A + Mat recon = l * u; + ReVal delta = pa.compare(recon) / n; + if(delta > detail::eps() * 50) {//quality of reconstruction shouldn't depend on condition number + os << "L * U != A for :\n" << a << "L:\n" << l << "U:\n" << u; + os << "L * U: " << recon << '\n'; + os << "P * A: " << pa << '\n'; + os << "error: " << delta << '\n'; + os << "condition number: " << cn << '\n'; + os << "eps: " << detail::eps() << '\n'; + return false; + } + + //solve a random system + std::vector b(n), x(n), ax(n); + std::uniform_real_distribution> dis(ReVal(-1), ReVal(1)); + for(Real& v : b) v = rndPhs(dis(gen), gen);//build random b + backsolve::lu(lu.data(), p.data(), x.data(), b.data(), n);//compute x + for(size_t i = 0; i < n; i++) ax[i] = std::inner_product(x.begin(), x.end(), a[i], Real(0));//compute a * x + + //make sure than a * x == b + delta = vectorCompare(ax, b) / n; + if(delta > detail::eps() * std::max>(cn, 50)) {//quality of back solution will depend on condition number (but give ourselves some breathing room for very small cn) + os << "A * x != b for LU:\n" << a << '\n'; + os << "\nx :"; for(auto v : x ) os << ' ' << v; + os << "\nb :"; for(auto v : b ) os << ' ' << v; + os << "\nAx:"; for(auto v : ax) os << ' ' << v; + os << "\nerror: " << delta << '\n'; + os << "condition number: " << cn << '\n'; + os << "eps: " << detail::eps() << '\n'; + return false; + } + return true; +} + +//@brief : test cholesky decomposition for a random matrix matrix +//@param os : location to write error messages +//@param n : matrix size +//@param cn : condition number to test for +//@param gen: random generator e.g. std::mt19937 +//@return : true/false if test passed/failed +template +bool testCholesky(std::ostream& os, const size_t n, const ReVal cn, URNG& gen) { + //create a random matrix positive/negative definite matrix + Mat a = randomDefinite(n, cn, gen, (gen() % 2) == 0); + + //do in place decomposition on a copy + Mat llt(a);//copy to keep a + std::vector d(n);//allocate diagonal matrix + const bool neg = decompose::cholesky(llt.data(), d.data(), n);//in place decompose + + //extract L + Mat l(llt); + for(size_t r = 0; r < n; r++) { + l[r][r] = d[r]; + for(size_t c = r+1; c < n; c++) { + l[r][c] = Real(0); + } + } + + //make sure that L * L^T == a + Mat recon = l * l.transpose(); + if(neg) for(Real& v : recon) v = -v;//handle negative definite matrices + ReVal delta = a.compare(recon) / n; + if(delta > detail::eps() * 20) { + os << "L * L^T != A for :\n" << a << "L:\n" << l; + os << "L * L^T: " << recon << '\n'; + os << "error: " << delta << '\n'; + os << "condition number: " << cn << '\n'; + os << "eps: " << detail::eps() << '\n'; + return false; + } + + //solve a random system + std::vector b(n), x(n), ax(n); + std::uniform_real_distribution> dis(ReVal(-1), ReVal(1)); + for(Real& v : b) v = rndPhs(dis(gen), gen);//build random b + backsolve::cholesky(llt.data(), d.data(), x.data(), b.data(), n, neg);//compute x + for(size_t i = 0; i < n; i++) ax[i] = std::inner_product(x.begin(), x.end(), a[i], Real(0));//compute a * x + + //make sure than a * x == b + delta = vectorCompare(ax, b) / n; + if(delta > detail::eps() * std::max>(cn, 10)) {//quality of back solution will depend on condition number (but give ourselves some breathing room for very small cn) + os << "A * x != b for cholesky:\n" << a << '\n'; + os << "\nx :"; for(auto v : x ) os << ' ' << v; + os << "\nb :"; for(auto v : b ) os << ' ' << v; + os << "\nAx:"; for(auto v : ax) os << ' ' << v; + os << "\nerror: " << delta << '\n'; + os << "condition number: " << cn << '\n'; + os << "eps: " << detail::eps() << '\n'; + return false; + } + return true; +} + +//@brief : test QR decomposition for a random mxn matrix +//@param os : location to write error messages +//@param m : number of rows +//@param n : number of columns +//@param gen: random generator e.g. std::mt19937 +//@return : true/false if test passed/failed +template +bool testQR(std::ostream& os, const size_t m, const size_t n, URNG& gen) { + //create a random matrix + Mat a(m,n); + std::uniform_real_distribution> dis(ReVal(-1), ReVal(1)); + for(Real& v : a) v = rndPhs(dis(gen), gen); + + //copy and do the in place QR decomposition + Mat qr(a); + decompose::qr(qr.data(), m, n); + + //copy decomposition and clear out lower half (build R) + Mat r(qr); + for(size_t i = 1; i < m; i++) + for(size_t j = 0; j < std::min(i,n); j++) + r[i][j] = Real(0); + + //reconstruct Q matrix + Mat q = Mat::Ident(m); + qr::applyQ(qr.data(), q.data(), m, n, m); + + //reconstruct Q^H matrix + Mat qh = Mat::Ident(m); + qr::applyQH(qr.data(), qh.data(), m, n, m); + + //compute q*r with matrix multiplication and q multiplication function + Mat aReconMult = q*r; + Mat aReconQ(r); + qr::applyQ(qr.data(), aReconQ.data(), m, n, n); + + //compute r with applyQH + Mat rReconQ(a); + qr::applyQH(qr.data(), rReconQ.data(), m, n, n); + + //make sure qh is actually Q^H + ReVal delta; + delta = q.compare(qh.conjugateTranspose()) / std::max(m, n); + if(delta > detail::eps()) { + os << "Q != Q^H^H != decomposition of\n" << a; + os << "Q:\n" << q << "Q^H:\n" << qh; + + + os << "\nerror: " << delta << '\n'; + os << "eps: " << detail::eps() << '\n'; + return false; + } + + //make sure Q * Q^H is identity (check that Q is orthogonal/unitary) + Mat qqh = q * qh; + delta = qqh.compare(Mat::Ident(m)) / std::max(m, n); + if(delta > detail::eps() * 10) { + os << "Q * Q^H != I for decomposition of\n" << a; + os << qqh; + os << "Q:\n" << q << "Q^H:\n" << qh; + + + os << "\nerror: " << delta << '\n'; + os << "eps: " << detail::eps() << '\n'; + return false; + } + + //make sure that Q R == A + delta = a.compare(aReconMult) / std::max(m, n); + ReVal eps = std::sqrt(detail::eps()); + if(delta > eps) { + os << "Q * R != A for decomposition of\n" << a; + os << "Q:\n" << q << "R:\n" << r << "Q*R\n" << aReconMult; + + + os << "\nerror: " << delta << '\n'; + os << "eps: " << detail::eps() << '\n'; + os << detail::eps() << '\n'; + return false; + } + + // make sure that QR computed through applyQ(R) == A + delta = a.compare(aReconQ) / std::max(m, n); + if(delta > eps) { + os << "applyQ(R) != A for decomposition of\n" << a; + os << "Q:\n" << q << "R:\n" << r << "Q*R\n" << aReconQ; + + + os << "\nerror: " << delta << '\n'; + os << "eps: " << detail::eps() << '\n'; + os << detail::eps() << '\n'; + return false; + } + + //make sure that R computed through applyQH(A) == R + delta = r.compare(rReconQ) / std::max(m, n); + if(delta > eps) { + os << "applyQH(A) != R for decomposition of\n" << a; + os << "Q^H:\n" << qh << "R:\n" << r << "Q^H*A\n" << rReconQ; + + + os << "\nerror: " << delta << '\n'; + os << "eps: " << detail::eps() << '\n'; + os << detail::eps() << '\n'; + return false; + } + return true; +} + +//@brief : test real valued linalg functions +//@return: true / false if tests pass / fail +template +bool testLinAlg(std::ostream& os) { + //seed random number generation + std::mt19937 gen(0);//deterministic behavior + + //build a range of difficulties (matrix condition numbers) + std::vector k; + std::vector> cond; + k.push_back(std::numeric_limits< ReVal >::digits10);//start from the worst possible round off error (in base 10 sigfigs), this is 6 for float, 15 for double + while(k.back() != 0) k.push_back(std::max(0, k.back() - 3));//build k in multiples of 3 (somewhat arbitrary) + for(const int& i : k) cond.push_back(std::pow(ReVal(10), i));//now convert from ~lost digits to condition number (10^k) + + //test QR decomposition for a range of sizes and condition numbers + os << "\ttesting QR" << std::endl; + for(size_t m = 1; m < 32; m++) { + for(size_t n = 1; n < 32; n++) { + if(!testQR(os, m, n, gen)) return false; + } + } + + //if QR passes we can use it to generate random unitary matrices + + //test LU decomposition for a range of sizes and condition numbers + os << "\ttesting LU" << std::endl; + for(size_t n = 1; n < 64; n++) { + for(const ReVal& c : cond) { + if(!testLU(os, n, c, gen)) return false; + } + } + + //test cholesky decomposition for a range of sizes and condition numbers + os << "\ttesting Cholesky" << std::endl; + cond.pop_back();//cholesky is less stable than lu, don't do the worst case + for(size_t n = 1; n < 64; n++) { + for(const ReVal& c : cond) { + if(!testCholesky(os, n, c, gen)) return false; + } + } + + //if we made it this far all tests passed + return true; +} diff --git a/test/util/nml.cpp b/test/util/nml.cpp new file mode 100644 index 0000000..ffa4a4f --- /dev/null +++ b/test/util/nml.cpp @@ -0,0 +1,346 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace nml { + //@brief : check if timer works + //@return: true / false if tests pass / fail + bool testNML(std::ostream& os); +} + +int main() { + try { + return nml::testNML(std::cout) ? EXIT_SUCCESS : EXIT_FAILURE; + } catch(std::exception& e) { + std::cout << "caught: " << e.what(); + return EXIT_FAILURE; + } +} + +#include +#include +#include +#include + +#include "util/nml.hpp" + +namespace nml { + + //@brief : check if timer works + //@return: true / false if tests pass / fail + bool testNML(std::ostream& os) { + //make a good namelist with one of each type + std::string testList; + testList += "! make sure we test a comment\n"; + + // add some booleans + testList += " vTrue = .true. ,\n"; + testList += " vFalse = .false.,\n"; + + //add some integers + testList += " vInt = 12345,\n"; + testList += " vIntPos = +12345,\n"; + testList += " vIntNeg = -12345,\n"; + + //add some doubles + testList += " vDoub = 1.2345,\n"; + testList += " vDoubPos = +1.2345,\n"; + testList += " vDoubNeg = -1.2345,\n"; + + //add some special doubles + const bool testNanInf = false;//nan and inf don't work on all platforms + if(testNanInf) { + testList += " vDoubNan = nan ,\n"; + testList += " vDoubInf = inf ,\n"; + } + testList += " vDoubSci = 1.23e4,\n"; + + testList += "! mix a comment in the middle\n"; + + //add some strings + testList += " vStr = 'str',\n"; + testList += " vStrSgl = 'str \\'with single quotes\\'',\n"; + testList += " vStrDbl = 'str \"with single quotes\"',\n"; + + //add a list of booleans + testList += " vBools = .true., .false., .false.,\n"; + testList += " vInts = 1, 2, 3 , 4,\n"; + testList += " vDoubles = 1, 2, 3., 4,\n";//a mix of doubles/integers should be cast up to doubles + testList += " vStrs = 'abc', '123', 'XYZ', '!@#',\n"; + + //////////////////////////////////////////////////////////////////////// + // // + // make sure a good namelist works correctly // + // // + //////////////////////////////////////////////////////////////////////// + + //wrap namelist and extract values + std::istringstream is(testList); + NameList nm; + nm.read(is); + + os << "checking scalar namelist parsing\n"; + + //check bool parsing + if(!nm.getBool("vTrue") || nm.getBool("vFalse")) { + os << "failed to extract bool from namelist\n"; + return false; + } + + //check in parsing + if(12345 != nm.getInt("vInt") || 12345 != nm.getInt("vIntPos") || -12345 != nm.getInt("vIntNeg")) { + os << "failed to extract int from namelist\n"; + return false; + } + + //check normal double parsing + if(1.2345 != nm.getDouble("vDoub") || 1.2345 != nm.getDouble("vDoubPos") ||-1.2345 != nm.getDouble("vDoubNeg") ) { + os << "failed to extract double from namelist\n"; + return false; + } + + //check special double parsing + const bool nanParsed = testNanInf ? std::isnan(nm.getDouble("vDoubNan")) : true; + const bool infParsed = testNanInf ? std::isinf(nm.getDouble("vDoubInf")) : true; + if(!nanParsed || !infParsed || 1.23e4 != nm.getDouble("vDoubSci")) { + os << "failed to extract special double from namelist\n"; + return false; + } + + //check string parsing + if("str" != nm.getString("vStr" ) || + "str 'with single quotes'" != nm.getString("vStrSgl") || + "str \"with single quotes\"" != nm.getString("vStrDbl") ) { + os << "failed to extract string from namelist\n"; + return false; + } + + //first check that a scalar parsed as a vector is correct + os << "checking scalar vector namelist parsing\n"; + std::vector bVec = nm.getBools("vTrue"); + std::vector iVec = nm.getInts("vInt"); + std::vector dVec = nm.getDoubles("vDoub"); + std::vector sVec = nm.getStrings("vStr"); + if(bVec.size() != 1 || iVec.size() != 1 || dVec.size() != 1 || sVec.size() != 1) { + os << "failed to extract scalar vectors from namelist\n"; + return false; + } + + //next check that vectors are parsed correctly + os << "checking scalar vector namelist parsing\n"; + bVec = nm.getBools("vBools"); + iVec = nm.getInts("vInts"); + dVec = nm.getDoubles("vDoubles"); + sVec = nm.getStrings("vStrs"); + if(3 != bVec.size() || 4 != iVec.size() || 4 != dVec.size() || 4 != sVec.size()) { + os << "failed to extract vector lengthss from namelist\n"; + return false; + } + + //check bools parsing + if(!bVec[0] || bVec[1] || bVec[2]) { + os << "failed to extract bool vec from namelist\n"; + return false; + } + + //check ints parsing + if(1 != iVec[0] || 2 != iVec[1] || 3 != iVec[2] || 4 != iVec[3]) { + os << "failed to extract int vec from namelist\n"; + return false; + } + + //check doubles parsing + if(1.0 != dVec[0] || 2.0 != dVec[1] || 3.0 != dVec[2] || 4.0 != dVec[3]) { + os << "failed to extract double vec from namelist\n"; + return false; + } + + //check strings parsing + if("abc" != sVec[0] || "123" != sVec[1] || "XYZ" != sVec[2] || "!@#" != sVec[3]) { + os << "failed to extract string vec from namelist\n"; + return false; + } + + //////////////////////////////////////////////////////////////////////// + // // + // make sure partial parsing detection works // + // // + //////////////////////////////////////////////////////////////////////// + + os << "checking scalar partial parsing detection\n"; + + //check full parsing flags on last file (false positive) + if(!nm.fullyParsed() || !nm.unusedTokens().empty()) { + os << "fully parsed check failed for fully parsed namelist\n"; + return false; + } + + //build a new file and partially parse + testList = "placeholder\n tokenOne = 1,\n tokenTwo = 2"; + is.str(testList);//update underlying string + is.clear();//clear bad bit + nm.read(is); + nm.getInt("tokenOne"); + + //make sure partial detection works (false negative) + if(nm.fullyParsed() || "tokentwo" != nm.unusedTokens()) { + os << "fully parsed check failed for partially parsed namelist\n"; + return false; + } + + //////////////////////////////////////////////////////////////////////// + // // + // test some potential edge cases // + // // + //////////////////////////////////////////////////////////////////////// + + os << "checking potential edge cases\n"; + + //make sure the first line isn't silently ignored + try { + testList = " key = 1\n"; + is.str(testList);//update underlying string + is.clear();//clear bad bit + nm.read(is); + os << "silently ignored first line with a value\n"; + return false; + } catch (...) {} + + //missing comma + try { + testList = "placeholder\n key = 1\n key2 = 2"; + is.str(testList);//update underlying string + is.clear();//clear bad bit + nm.read(is); + os << "failed to detect missing comma at end of line\n"; + return false; + } catch (...) {} + + //missing leading space + try { + testList = "placeholder\nkey = 1\n"; + is.str(testList);//update underlying string + is.clear();//clear bad bit + nm.read(is); + os << "failed to detect missing leading space\n"; + return false; + } catch (...) {} + + //duplicate key + try { + testList = "placeholder\n key = 1,\n key = 2"; + is.str(testList);//update underlying string + is.clear();//clear bad bit + nm.read(is); + os << "failed to detect duplicate key\n"; + return false; + } catch (...) {} + + //missing '=' + try { + testList = "placeholder\n key = 1,\n key2 2"; + is.str(testList);//update underlying string + is.clear();//clear bad bit + nm.read(is); + os << "failed to detect missing '='\n"; + return false; + } catch (...) {} + + //missing string delimiter + try { + testList = "placeholder\n key = '1' '2'\n"; + is.str(testList);//update underlying string + is.clear();//clear bad bit + nm.read(is); + os << "failed to detect missing string delimiter\n"; + return false; + } catch (...) {} + + //bad string opening in list + try { + testList = "placeholder\n key = '1', 2\n"; + is.str(testList);//update underlying string + is.clear();//clear bad bit + nm.read(is); + os << "failed to detect bad string opening in list\n"; + return false; + } catch (...) {} + + //double quoted string + try { + testList = "placeholder\n key = \"1\"\n"; + is.str(testList);//update underlying string + is.clear();//clear bad bit + nm.read(is); + os << "failed to detect double quoted string\n"; + return false; + } catch (...) {} + + //unquoted string + try { + testList = "placeholder\n key = value\n"; + is.str(testList);//update underlying string + is.clear();//clear bad bit + nm.read(is); + os << "failed to detect unquoted string\n"; + return false; + } catch (...) {} + + //int/bool mix + try { + testList = "placeholder\n key = 1, .true.\n"; + is.str(testList);//update underlying string + is.clear();//clear bad bit + nm.read(is); + os << "failed to detect int/bool mix\n"; + return false; + } catch (...) {} + + //double/bool mix + try { + testList = "placeholder\n key = 1.0, .true.\n"; + is.str(testList);//update underlying string + is.clear();//clear bad bit + nm.read(is); + os << "failed to detect double/bool mix\n"; + return false; + } catch (...) {} + + //(string mixing is allowed) + + //if we made it this far all tests passed + return true; + } +} diff --git a/test/util/threadpool.cpp b/test/util/threadpool.cpp new file mode 100644 index 0000000..dc060a2 --- /dev/null +++ b/test/util/threadpool.cpp @@ -0,0 +1,169 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +//@brief : check if thead pool works +//@return: true / false if tests pass / fail +bool testThreadPool(std::ostream& os); + +int main() { + return testThreadPool(std::cout) ? EXIT_SUCCESS : EXIT_FAILURE; +} + +#include "util/threadpool.hpp" +#include "util/timer.hpp" + +#include +#include + +//@brief : check if thead pool works +//@return: true / false if tests pass / fail +bool testThreadPool(std::ostream& os) { + //build a thread pool + ThreadPool pool; + os << "checking if serial and parallel calculations are consistent\n"; + + //make a function to do some work + std::vector buff(pool.size() * pool.size(), 0); + auto func = [&](const size_t idx, const size_t tid) {buff[idx] = 1;}; + + //make sure serial and calculation works + for(size_t i = 0; i < buff.size(); i++) func(i, 0); + if(!std::all_of(buff.cbegin(), buff.cend(), [](const int& v){return v == 1;})) { + os << "got wrong answer from serial computation\n"; + return false; + } + std::fill(buff.begin(), buff.end(), 0); + + //make sure the parallel fill is correct + for(size_t i = 0; i < buff.size(); i++) pool.schedule(std::bind(func, i, std::placeholders::_1)); + pool.waitAll(); + if(!std::all_of(buff.cbegin(), buff.cend(), [](const int& v){return v == 1;})) { + os << "got wrong answer from parallel computation\n"; + return false; + } + std::fill(buff.begin(), buff.end(), 0); + + //make sure that thread index is working for passed functions + os << "checking if thread index works correctly\n"; + std::vector< std::mutex > muts(pool.size());//1 mutex per thread + auto funcMut = [&](const size_t tid) { + for(size_t i = 0; i < pool.size(); i++) { + if(!muts[tid].try_lock()) {//try to get this thread's mutex (there better not be any competition) + os << "failed to lock thread mutex\n"; + os.flush(); + std::terminate(); + } + std::this_thread::sleep_for(std::chrono::milliseconds(100));//hold the lock for awhile + muts[tid].unlock();//release the lock for the next loop + } + }; + for(size_t i = 0; i < buff.size(); i++) pool.schedule(funcMut); + pool.waitAll(); + + //make a slow function to burn time + os << "checking speedup\n"; + auto funcWait = [&](const size_t tid) {std::this_thread::sleep_for(std::chrono::milliseconds(100));};//sleep for 0.1s + Timer t; + for(size_t i = 0; i < 10; i++) {//10 * 0.1 = 1s of work per thread + for(size_t j = 0; j < pool.size(); j++) pool.schedule(funcWait); + } + pool.waitAll(); + double tWrk = t.poll(); + if(std::fabs(tWrk - 1.0) > 0.02) {//2% error + os << "1s of work per thread took " << tWrk << "s\n"; + return false; + } + + //try the same thing with a single worker pool + os << "checking destructor\n"; + { + ThreadPool pool1(1); + t.poll(); + pool1.schedule([&](const size_t tid){ + buff[0] = 0; + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + buff[0] = 1; + }); + }//test that destructor calls waitAll() + tWrk = t.poll(); + if(1 != buff[0]) { + os << "destructor didn't wait for thread completion\n"; + return false; + } + if(std::fabs(tWrk - 1.0) > 0.02) {//2% error + os << "1s of work for 1 thread thread took " << tWrk << "s\n"; + return false; + } + + //make sure timeout works + os << "checking timeout\n"; + pool.schedule([&](const size_t tid){std::this_thread::sleep_for(std::chrono::milliseconds(2000));});//2s of work + t.poll(); + const bool done = pool.waitAll(std::chrono::milliseconds(500));//give the thread only 0.5s to finish + tWrk = t.poll(); + if(done) { + os << "too short waitout returned true\n"; + return false; + } + if(std::fabs(tWrk - 0.5) > 0.01) {//2% error + os << "short waitout isn't accurate\n"; + return false; + } + pool.waitAll(); + + //make sure task clearing works + os << "checking task clearing\n"; + for(size_t i = 0; i < pool.size(); i++)//schedule short piece of work for every thread + pool.schedule([&](const size_t tid){std::this_thread::sleep_for(std::chrono::milliseconds(600));});//0.5s of work + for(size_t i = 0; i < pool.size(); i++)//schedule long piece of work for every thread + pool.schedule([&](const size_t tid){std::this_thread::sleep_for(std::chrono::milliseconds(5000));});//5s of work + std::this_thread::sleep_for(std::chrono::milliseconds(100));//give sleeping workers a chance to wake up + if(pool.size() * 2 != pool.items()) {//make sure no workers have finished the short task and started the long task + os << "unexpected queuing time\n"; + return false; + } + pool.clear();//remove the long work (shouldn't be started by any threads yet) + t.poll(); + pool.waitAll();//wait for all the work to finish + tWrk = t.poll(); + if(std::fabs(tWrk - 0.5) > 0.02) {//4% error + os << "task clearing didn't remove unstarted work: " << tWrk << "\n"; + return false; + } + + //if we made it this far all tests passed + return true; +} diff --git a/test/util/timer.cpp b/test/util/timer.cpp new file mode 100644 index 0000000..718d8fd --- /dev/null +++ b/test/util/timer.cpp @@ -0,0 +1,81 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +//@brief : check if timer works +//@return: true / false if tests pass / fail +bool testTimer(std::ostream& os); + +int main() { + return testTimer(std::cout) ? EXIT_SUCCESS : EXIT_FAILURE; +} + +#include +#include +#include +#include + +#include "util/timer.hpp" + +//@brief : check if timer works +//@return: true / false if tests pass / fail +bool testTimer(std::ostream& os) { + Timer t;//build a timer + std::vector times(3);//build space to store timing results (3 trials) + for(size_t ms = 8; ms < 4096; ms *= 2) { + //convert from ms to fractional seconds and print info + const double dMs = double(ms) / 1000; + os << "testing " << times.size() << " * " << dMs << "s: "; + os.flush(); + + //time sleep for a few runs + for(double& v : times) { + t.poll(); + std::this_thread::sleep_for(std::chrono::milliseconds(ms)); + v = t.poll(); + } + double v = std::accumulate(times.cbegin(), times.cend(), 0.0) / times.size(); + + //print result and check + std::cout << v << '\n'; + if(std::fabs(dMs - v) * 1000 > 6) { + os << "error outside of 4ms tolerance\n"; + return false; + } + } + + //if we made it this far all tests passed + return true; +} diff --git a/test/xtal/emsoft_gen.hpp b/test/xtal/emsoft_gen.hpp new file mode 100644 index 0000000..db921ea --- /dev/null +++ b/test/xtal/emsoft_gen.hpp @@ -0,0 +1,506 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _emgen_h_ +#define _emgen_h_ + +#include +#include + +#include "xtal/position.hpp" + +namespace emsoft { + + //@brief : compress an EMsoft generator string into a 64 bit integer + //@param gen: 40 character EMsoft generator string to compress + //@return : uint64_t representation of generator string + uint64_t encode(std::string gen); + + //@brief : decompress an EMsoft generator string from a 64 bit integer + //@param enc: compressed generator string + //@return : EMsoft generator string + std::string decode(uint64_t enc); + + //@brief : build generator matricies from compressed EMsoft generator + //@param enc: compressed generator string + //@param alt: should the alternate origin be used + //@return : 4x4 generator matricies (empty if alt = true but there is only 1 origin) + std::vector gen_from_enc(uint64_t enc, const bool alt = false); + + //@brief : build generator matricies for a space group + //@param sg : space group number [1,230] + //@param alt: should an alternate setting be selected (rhombohedral instead of hex or origin choice 2 instead of 1 as appropriate) + //@return : 4x4 generator matricies (empty if alt = true but there is only 1 setting) + std::vector gen_from_num(size_t sg, const bool alt = false); + + //@brief : build generator matricies for an extended monoclinic space group + //@param sg : space group number [3,15] + //@param cell: cell choice [1,3] + //@param axs : unique axis {"b", "-b", "c", "-c", "a", or "-a"} + //@return : 4x4 generator matricies (empty if alt = true but there is only 1 setting) + std::vector mono_gen_from_num(size_t sg, size_t cell, std::string axs = "b"); + + //@brief : build generator matricies for an extended orthorhombic space group + //@param sg : space group number [16,74] + //@param axs : axis configuration {"abc", "bac", "cab", "cba", "bca", or "acb"} + //@return : 4x4 generator matricies (empty if alt = true but there is only 1 setting) + //@warning : unverified for space groups [51,74] and currently origin 1 only + std::vector ortho_gen_from_num(size_t sg, std::string axs); + + //table of (short) space group names + static const std::vector SGNames = { + "P 1 " ,"P -1 ", + "P 2 " ,"P 21 " ,"C 2 " ,"P m ", + "P c " ,"C m " ,"C c " ,"P 2/m ", + "P 21/m " ,"C 2/m " ,"P 2/c " ,"P 21/c ", + "C 2/c ", + "P 2 2 2 " ,"P 2 2 21 " ,"P 21 21 2 " ,"P 21 21 21", + "C 2 2 21 " ,"C 2 2 2 " ,"F 2 2 2 " ,"I 2 2 2 ", + "I 21 21 21" ,"P m m 2 " ,"P m c 21 " ,"P c c 2 ", + "P m a 2 " ,"P c a 21 " ,"P n c 2 " ,"P m n 21 ", + "P b a 2 " ,"P n a 21 " ,"P n n 2 " ,"C m m 2 ", + "C m c 21 " ,"C c c 2 " ,"A m m 2 " ,"A e m 2 ", // A b m 2 ==> A e m 2 + "A m a 2 " ,"A e a 2 " ,"F m m 2 " ,"F d d 2 ", // A b a 2 ==> A e a 2 + "I m m 2 " ,"I b a 2 " ,"I m a 2 " ,"P m m m ", + "P n n n " ,"P c c m " ,"P b a n " ,"P m m a ", + "P n n a " ,"P m n a " ,"P c c a " ,"P b a m ", + "P c c n " ,"P b c m " ,"P n n m " ,"P m m n ", + "P b c n " ,"P b c a " ,"P n m a " ,"C m c m ", + "C m c e " ,"C m m m " ,"C c c m " ,"C m m e ",// C m (c/m) a ==> C m (c/m) e + "C c c e " ,"F m m m " ,"F d d d " ,"I m m m ",// C c c a ==> C c c e + "I b a m " ,"I b c a " ,"I m m a ", + "P 4 " ,"P 41 " ,"P 42 " ,"P 43 ", + "I 4 " ,"I 41 " ,"P -4 " ,"I -4 ", + "P 4/m " ,"P 42/m " ,"P 4/n " ,"P 42/n ", + "I 4/m " ,"I 41/a " ,"P 4 2 2 " ,"P 4 21 2 ", + "P 41 2 2 " ,"P 41 21 2 " ,"P 42 2 2 " ,"P 42 21 2 ", + "P 43 2 2 " ,"P 43 21 2 " ,"I 4 2 2 " ,"I 41 2 2 ", + "P 4 m m " ,"P 4 b m " ,"P 42 c m " ,"P 42 n m ", + "P 4 c c " ,"P 4 n c " ,"P 42 m c " ,"P 42 b c ", + "I 4 m m " ,"I 4 c m " ,"I 41 m d " ,"I 41 c d ", + "P -4 2 m " ,"P -4 2 c " ,"P -4 21 m " ,"P -4 21 c ", + "P -4 m 2 " ,"P -4 c 2 " ,"P -4 b 2 " ,"P -4 n 2 ", + "I -4 m 2 " ,"I -4 c 2 " ,"I -4 2 m " ,"I -4 2 d ", + "P 4/m m m " ,"P 4/m c c " ,"P 4/n b m " ,"P 4/n n c ", + "P 4/m b m " ,"P 4/m n c " ,"P 4/n m m " ,"P 4/n c c ", + "P 42/m m c" ,"P 42/m c m" ,"P 42/n b c" ,"P 42/n n m", + "P 42/m b c" ,"P 42/m n m" ,"P 42/n m c" ,"P 42/n c m", + "I 4/m m m " ,"I 4/m c m " ,"I 41/a m d" ,"I 41/a c d", + "P 3 " ,"P 31 " ,"P 32 " ,"R 3 ", + "P -3 " ,"R -3 " ,"P 3 1 2 " ,"P 3 2 1 ", + "P 31 1 2 " ,"P 31 2 1 " ,"P 32 1 2 " ,"P 32 2 1 ", + "R 3 2 " ,"P 3 m 1 " ,"P 3 1 m " ,"P 3 c 1 ", + "P 3 1 c " ,"R 3 m " ,"R 3 c " ,"P -3 1 m ", + "P -3 1 c " ,"P -3 m 1 " ,"P -3 c 1 " ,"R -3 m ", + "R -3 c ", + "P 6 " ,"P 61 " ,"P 65 " ,"P 62 ", + "P 64 " ,"P 63 " ,"P -6 " ,"P 6/m ", + "P 63/m " ,"P 6 2 2 " ,"P 61 2 2 " ,"P 65 2 2 ", + "P 62 2 2 " ,"P 64 2 2 " ,"P 63 2 2 " ,"P 6 m m ", + "P 6 c c " ,"P 63 c m " ,"P 63 m c " ,"P -6 m 2 ", + "P -6 c 2 " ,"P -6 2 m " ,"P -6 2 c " ,"P 6/m m m ", + "P 6/m c c " ,"P 63/m c m" ,"P 63/m m c", + "P 2 3 " ,"F 2 3 " ,"I 2 3 " ,"P 21 3 ", + "I 21 3 " ,"P m 3 " ,"P n 3 " ,"F m 3 ", + "F d 3 " ,"I m 3 " ,"P a 3 " ,"I a 3 ", + "P 4 3 2 " ,"P 42 3 2 " ,"F 4 3 2 " ,"F 41 3 2 ", + "I 4 3 2 " ,"P 43 3 2 " ,"P 41 3 2 " ,"I 41 3 2 ", + "P -4 3 m " ,"F -4 3 m " ,"I -4 3 m " ,"P -4 3 n ", + "F -4 3 c " ,"I -4 3 d " ,"P m 3 m " ,"P n 3 n ", + "P m 3 n " ,"P n 3 m " ,"F m 3 m " ,"F m 3 c ", + "F d 3 m " ,"F d 3 c " ,"I m 3 m " ,"I a 3 d ", + }; + +} + +#include +#include + +namespace emsoft { + + //lookup table of all elements of generator strings + //even though there are 15 possible 3x3 matricies characters and 11 translation characters there are only 86 unique 4 character codes + //this is a table of all of them in alphabetical order + static const uint8_t NumBlocks = 86+23+2; + static const char GenLut[NumBlocks][5] = { + " ", "1BBB", "1BBO", "1OBB", "1OBZ", "1OYZ", "1XXX", "1YBO", + "1YBY", "1YYO", "1YYY", "1ZZZ", "aDDD", "aDDO", "aDOD", "aECC", + "aODD", "bDDD", "bDDO", "bDOD", "bDOO", "bODD", "bODO", "bOOD", + "bOOO", "cDDB", "cDDD", "cDDF", "cDDO", "cDOB", "cDOD", "cDOF", + "cODD", "cODO", "cOOD", "cOOO", "dOOO", "eBFF", "eDDD", "eFBB", + "eFBF", "eOOC", "eOOD", "eOOE", "eOOO", "fDDD", "fOOC", "fOOD", + "fOOE", "fOOO", "gDDB", "gDDD", "gDDF", "gDDO", "gODB", "gOOB", + "gOOD", "gOOF", "gOOO", "hBBB", "hDDD", "hDDO", "hFFF", "hODB", + "hODD", "iOOD", "iOOO", "jBBB", "jDDD", "jDDO", "jDOD", "jDOO", + "jODD", "jODO", "jOOD", "jOOO", "kOOD", "kOOO", "lBBB", "lDDD", + "lOOD", "lOOO", "mOOO", "nOOC", "nOOE", "nOOO", + + //additions for extended monoclinic settings + "cDOO", "iDDD", + "iDDO", "iDOD", "iDOO", "iODD", "iODO", "oDDD", "oDDO", "oDOD", + "oDOO", "oODD", "oODO", "oOOD", "oOOO", "pDDD", "pDDO", "pDOD", + "pDOO", "pODD", "pODO", "pOOD", "pOOO", + + //additions for extended orthorhombic settings + "iBBB", "pBBB", + }; + + //lookup table of encoded generator strings for all space groups (+7 extra for rhombohedral settings) + //this is a sequence of 8 bytes with each one indexing into the GenLut table (or 0x00 for no element ==> identity) + //each byte is actually a 7 bit integer with the leading bit reserved + //currently the first bit on the first byte is the inversion symmetry flag so 0x0 => 0x7 for no inversion, 0x08 => 0xf yes inversion + //this is ~4x smaller than the full string representation + static const uint64_t SGLut[237] = { + 0x0000000000000000,0x8000000000000000,0x2300000000000000,0x2100000000000000,0x0d23000000000000,0x4b00000000000000, + 0x4a00000000000000,0x0d4b000000000000,0x0d4a000000000000,0xa300000000000000,0xa100000000000000,0x8d23000000000000, + 0xa200000000000000,0xa000000000000000,0x8d22000000000000,0x1823000000000000,0x1722000000000000,0x181c000000000000, + 0x1320000000000000,0x0d17220000000000,0x0d18230000000000,0x100e182300000000,0x0c18230000000000,0x0c13200000000000, + 0x184b000000000000,0x174a000000000000,0x184a000000000000,0x1847000000000000,0x1747000000000000,0x1848000000000000, + 0x1346000000000000,0x1845000000000000,0x1745000000000000,0x1844000000000000,0x0d184b0000000000,0x0d174a0000000000, + 0x0d184a0000000000,0x10184b0000000000,0x1018490000000000,0x1018470000000000,0x1018450000000000,0x100e184b00000000, + 0x100e184300000000,0x0c184b0000000000,0x0c18450000000000,0x0c18470000000000,0x9823000000000000,0x18233c0100000000, + 0x9822000000000000,0x18233d0200000000,0x9423000000000000,0x941a000000000000,0x931e000000000000,0x9422000000000000, + 0x981c000000000000,0x9220000000000000,0x9720000000000000,0x981a000000000000,0x181c3d0200000000,0x9122000000000000, + 0x9320000000000000,0x9321000000000000,0x8d17220000000000,0x8d15200000000000,0x8d18230000000000,0x8d18220000000000, + 0x8d16210000000000,0x0d12234003000000,0x900e182300000000,0x100e18233b0b0000,0x8c18230000000000,0x8c181c0000000000, + 0x8c13200000000000,0x8c16210000000000,0x183a000000000000,0x1737000000000000,0x1838000000000000,0x1739000000000000, + 0x0c183a0000000000,0x0c11360000000000,0x1852000000000000,0x0c18520000000000,0x983a000000000000,0x9838000000000000, + 0x18353d0700000000,0x18333c0a00000000,0x8c183a0000000000,0x0c11363f05000000,0x183a230000000000,0x18351c0000000000, + 0x1737230000000000,0x1732190000000000,0x1838230000000000,0x18331a0000000000,0x1739230000000000,0x17341b0000000000, + 0x0c183a2300000000,0x0c11361f00000000,0x183a4b0000000000,0x183a450000000000,0x18384a0000000000,0x1833440000000000, + 0x183a4a0000000000,0x183a440000000000,0x18384b0000000000,0x1838450000000000,0x0c183a4b00000000,0x0c183a4a00000000, + 0x0c11364b00000000,0x0c11364a00000000,0x1852230000000000,0x1852220000000000,0x18521c0000000000,0x18521a0000000000, + 0x18524b0000000000,0x18524a0000000000,0x1852450000000000,0x1852440000000000,0x0c18524b00000000,0x0c18524a00000000, + 0x0c18522300000000,0x0c18521f00000000,0x983a230000000000,0x983a220000000000,0x183a233d09000000,0x183a233c0a000000, + 0x983a1c0000000000,0x983a1a0000000000,0x18351c3d07000000,0x18351a3d07000000,0x9838230000000000,0x9838220000000000, + 0x1833223c08000000,0x1833233c08000000,0x98381c0000000000,0x98331a0000000000,0x18331a3c08000000,0x18331c3c08000000, + 0x8c183a2300000000,0x8c183a2200000000,0x0c11361f3f040000,0x0c11361d3f040000,0x5500000000000000,0x5300000000000000, + 0x5400000000000000,0x0f55000000000000,0xd500000000000000,0x8f55000000000000,0x5531000000000000,0x552c000000000000, + 0x5330000000000000,0x532c000000000000,0x542e000000000000,0x542c000000000000,0x0f552c0000000000,0x554d000000000000, + 0x5551000000000000,0x554c000000000000,0x5550000000000000,0x0f554d0000000000,0x0f554c0000000000,0xd531000000000000, + 0xd52f000000000000,0xd52c000000000000,0xd52a000000000000,0x8f552c0000000000,0x8f552a0000000000,0x5518000000000000, + 0x5317000000000000,0x5417000000000000,0x5418000000000000,0x5318000000000000,0x5517000000000000,0x5542000000000000, + 0xd518000000000000,0xd517000000000000,0x55182c0000000000,0x5317290000000000,0x54172b0000000000,0x54182b0000000000, + 0x5318290000000000,0x55172c0000000000,0x55184d0000000000,0x55184c0000000000,0x55174c0000000000,0x55174d0000000000, + 0x55424d0000000000,0x55414c0000000000,0x55422c0000000000,0x55412c0000000000,0xd5182c0000000000,0xd5182a0000000000, + 0xd5172a0000000000,0xd5172c0000000000,0x1823240000000000,0x100e182324000000,0x0c18232400000000,0x1320240000000000, + 0x0c13202400000000,0x9823240000000000,0x1823243c0a000000,0x900e182324000000,0x100e1823243b0b00,0x8c18232400000000, + 0x9320240000000000,0x8c13202400000000,0x1823242c00000000,0x1823242600000000,0x100e1823242c0000,0x100e151c24280000, + 0x0c1823242c000000,0x1320242500000000,0x1320242700000000,0x0c13202427000000,0x1823245100000000,0x100e182324510000, + 0x0c18232451000000,0x1823244f00000000,0x100e1823244f0000,0x0c1320244e000000,0x9823242c00000000,0x1823242c3c0a0000, + 0x9823242600000000,0x182324263c0a0000,0x900e1823242c0000,0x900e182324260000,0x100e151c24283b0b,0x100e151c24283e06, + 0x8c1823242c000000,0x8c13202427000000,0x2400000000000000,0xa400000000000000,0x2431000000000000,0x2451000000000000, + 0x244f000000000000,0xa431000000000000,0xa42d000000000000 + }; + + //extended settings for monoclinic space groups + //these have been verified against the bilbao crystallography server: http://www.cryst.ehu.es/ + static const uint32_t MonoLut[13][6][3] = { + { //space group 3 + // cell 1 , cell 2 , cell 3 + {0x23000000, 0x23000000, 0x23000000},{0x23000000, 0x23000000, 0x23000000},//unique axis +/-b + {0x18000000, 0x18000000, 0x18000000},{0x18000000, 0x18000000, 0x18000000},//unique axis +/-c + {0x64000000, 0x64000000, 0x64000000},{0x64000000, 0x64000000, 0x64000000},//unique axis +/-a + },{//space group 4 + {0x21000000, 0x21000000, 0x21000000},{0x21000000, 0x21000000, 0x21000000}, + {0x17000000, 0x17000000, 0x17000000},{0x17000000, 0x17000000, 0x17000000}, + {0x60000000, 0x60000000, 0x60000000},{0x60000000, 0x60000000, 0x60000000}, + },{//space group 5 + {0x230D0000, 0x23100000, 0x230C0000},{0x23100000, 0x230D0000, 0x230C0000}, + {0x18100000, 0x180E0000, 0x180C0000},{0x180E0000, 0x18100000, 0x180C0000}, + {0x640E0000, 0x640D0000, 0x640C0000},{0x640D0000, 0x640E0000, 0x640C0000}, + },{//space group 6 + {0x4B000000, 0x4B000000, 0x4B000000},{0x4B000000, 0x4B000000, 0x4B000000}, + {0x42000000, 0x42000000, 0x42000000},{0x42000000, 0x42000000, 0x42000000}, + {0x6C000000, 0x6C000000, 0x6C000000},{0x6C000000, 0x6C000000, 0x6C000000}, + },{//space group 7 + {0x4A000000, 0x46000000, 0x47000000},{0x47000000, 0x46000000, 0x4A000000}, + {0x5A000000, 0x58000000, 0x5C000000},{0x5C000000, 0x58000000, 0x5A000000}, + {0x6A000000, 0x69000000, 0x6B000000},{0x6B000000, 0x69000000, 0x6A000000}, + },{//space group 8 + {0x4B0D0000, 0x4B100000, 0x4B0C0000},{0x4B100000, 0x4B0D0000, 0x4B0C0000}, + {0x42100000, 0x420E0000, 0x420C0000},{0x420E0000, 0x42100000, 0x420C0000}, + {0x6C0E0000, 0x6C0D0000, 0x6C0C0000},{0x6C0D0000, 0x6C0E0000, 0x6C0C0000}, + },{//space group 9 + {0x4A0D0000, 0x10460000, 0x470C0000},{0x10470000, 0x460D0000, 0x4A0C0000}, + {0x105A0000, 0x0E580000, 0x5C0C0000},{0x5C0E0000, 0x10580000, 0x5A0C0000}, + {0x6A0E0000, 0x690D0000, 0x6B0C0000},{0x6B0D0000, 0x690E0000, 0x6A0C0000}, + },{//space group 10 + {0xCB230000, 0xCB230000, 0xCB230000},{0xCB230000, 0xCB230000, 0xCB230000}, + {0x98420000, 0x98420000, 0x98420000},{0x98420000, 0x98420000, 0x98420000}, + {0xEC640000, 0xEC640000, 0xEC640000},{0xEC640000, 0xEC640000, 0xEC640000}, + },{//space group 11 + {0xC9210000, 0xC9210000, 0xC9210000},{0xC9210000, 0xC9210000, 0xC9210000}, + {0xC1170000, 0xC1170000, 0xC1170000},{0xC1170000, 0xC1170000, 0xC1170000}, + {0xE8600000, 0xE8600000, 0xE8600000},{0xE8600000, 0xE8600000, 0xE8600000}, + },{//space group 12 + {0xCB230D00, 0xCB231000, 0xCB230C00},{0xCB231000, 0xCB230D00, 0xCB230C00}, + {0x98421000, 0x98420E00, 0x98420C00},{0x98420E00, 0x98421000, 0x98420C00}, + {0xEC640E00, 0xEC640D00, 0xEC640C00},{0xEC640D00, 0xEC640E00, 0xEC640C00}, + },{//space group 13 + {0xA24A0000, 0x9E460000, 0xD6470000},{0xD6470000, 0x9E460000, 0xA24A0000}, + {0x945A0000, 0x92580000, 0x965C0000},{0x965C0000, 0x92580000, 0x945A0000}, + {0xE26A0000, 0xE1690000, 0xE36B0000},{0xE36B0000, 0xE1690000, 0xE26A0000}, + },{//space group 14 + {0xC8200000, 0x9A440000, 0x9C450000},{0x9C450000, 0x9A440000, 0xC8200000}, + {0x93590000, 0x91570000, 0x955B0000},{0x955B0000, 0x91570000, 0x93590000}, + {0xE65E0000, 0xE55D0000, 0xE75F0000},{0xE75F0000, 0xE55D0000, 0xE65E0000}, + },{//space group 15 + {0xA24A0D00, 0x9E104600, 0xD6470C00},{0xD6104700, 0x9E460D00, 0xA24A0C00}, + {0x94105A00, 0x920E5800, 0x965C0C00},{0x965C0E00, 0x92105800, 0x945A0C00}, + {0xE26A0E00, 0xE1690D00, 0xE36B0C00},{0xE36B0D00, 0xE1690E00, 0xE26A0C00}, + } + }; + + //extended settings for orthorhombic space groups (currently only origin choice 1) + //these have been PARTIALLY verified against the bilbao crystallography server: http://www.cryst.ehu.es/ + static const uint64_t OrthoLut[59][6] = { + // abc bac cab cba bca acb + //222 type groups + {0x2364000000000000,0x2364000000000000,0x2364000000000000,0x2364000000000000,0x2364000000000000,0x2364000000000000},//16 + {0x6422000000000000,0x6422000000000000,0x2314000000000000,0x2314000000000000,0x1862000000000000,0x1862000000000000},//17 + {0x5e1c000000000000,0x5e1c000000000000,0x2015000000000000,0x2015000000000000,0x5f13000000000000,0x5f13000000000000},//18 + {0x5e20000000000000,0x5e20000000000000,0x5e20000000000000,0x5e20000000000000,0x5e20000000000000,0x5e20000000000000},//19 + {0x640d220000000000,0x640d220000000000,0x2314100000000000,0x2314100000000000,0x18620e0000000000,0x18620e0000000000},//20 + {0x23640d0000000000,0x23640d0000000000,0x23640d0000000000,0x23640d0000000000,0x23640d0000000000,0x23640d0000000000},//21 + {0x23640e1000000000,0x23640e1000000000,0x23640e1000000000,0x23640e1000000000,0x23640e1000000000,0x23640e1000000000},//22 + {0x23640c0000000000,0x23640c0000000000,0x23640c0000000000,0x23640c0000000000,0x23640c0000000000,0x23640c0000000000},//23 + {0x5e200c0000000000,0x5e200c0000000000,0x5e200c0000000000,0x5e200c0000000000,0x5e200c0000000000,0x5e200c0000000000},//24 + //mm2 type groups + {0x6c4b000000000000,0x6c4b000000000000,0x4b42000000000000,0x4b42000000000000,0x6c42000000000000,0x6c42000000000000},//25 + {0x6c4a000000000000,0x4b6b000000000000,0x4b5a000000000000,0x4247000000000000,0x426a000000000000,0x6c5c000000000000},//26 + {0x6b4a000000000000,0x6b4a000000000000,0x475a000000000000,0x475a000000000000,0x6a5c000000000000,0x6a5c000000000000},//27 + {0x6847000000000000,0x496a000000000000,0x495c000000000000,0x414a000000000000,0x416b000000000000,0x685a000000000000},//28 + {0x4767000000000000,0x6a48000000000000,0x455c000000000000,0x594a000000000000,0x5b6b000000000000,0x5a66000000000000},//29 + {0x4869000000000000,0x6746000000000000,0x5946000000000000,0x4558000000000000,0x6658000000000000,0x5b69000000000000},//30 + {0x6c46000000000000,0x4b69000000000000,0x4b58000000000000,0x4246000000000000,0x4269000000000000,0x6c58000000000000},//31 + {0x4566000000000000,0x4566000000000000,0x5b48000000000000,0x5b48000000000000,0x5967000000000000,0x5967000000000000},//32 + {0x4565000000000000,0x6644000000000000,0x5b44000000000000,0x5748000000000000,0x5767000000000000,0x5965000000000000},//33 + {0x4465000000000000,0x4465000000000000,0x5744000000000000,0x5744000000000000,0x5765000000000000,0x5765000000000000},//34 + {0x6c4b0d0000000000,0x6c4b0d0000000000,0x4b42100000000000,0x4b42100000000000,0x6c420e0000000000,0x6c420e0000000000},//35 + {0x6c0d4a0000000000,0x4b0d6b0000000000,0x4b5a100000000000,0x4247100000000000,0x426a0e0000000000,0x6c5c0e0000000000},//36 + {0x0d6b4a0000000000,0x0d6b4a0000000000,0x475a100000000000,0x475a100000000000,0x6a5c0e0000000000,0x6a5c0e0000000000},//37 + {0x6c4b100000000000,0x6c4b0e0000000000,0x4b420e0000000000,0x4b420d0000000000,0x6c420d0000000000,0x6c42100000000000},//38 + {0x496a6b1000000000,0x68474a0e00000000,0x41474a0e00000000,0x495a5c0d00000000,0x685a5c0d00000000,0x416a6b1000000000},//39 + {0x6847100000000000,0x496a0e0000000000,0x495c0e0000000000,0x410d4a0000000000,0x410d6b0000000000,0x685a100000000000},//40 + {0x4566671000000000,0x4566480e00000000,0x455b480e00000000,0x595b0d4800000000,0x595b0d6700000000,0x5966671000000000},//41 + {0x6c4b0e1000000000,0x6c4b0e1000000000,0x4b420e1000000000,0x4b420e1000000000,0x6c420e1000000000,0x6c420e1000000000},//42 + {0x436e0e1000000000,0x436e0e1000000000,0x6d430e1000000000,0x6d430e1000000000,0x6d6e0e1000000000,0x6d6e0e1000000000},//43 + {0x6c4b0c0000000000,0x6c4b0c0000000000,0x4b420c0000000000,0x4b420c0000000000,0x6c420c0000000000,0x6c420c0000000000},//44 + {0x45660c0000000000,0x45660c0000000000,0x5b480c0000000000,0x5b480c0000000000,0x59670c0000000000,0x59670c0000000000},//45 + {0x68470c0000000000,0x496a0c0000000000,0x495c0c0000000000,0x414a0c0000000000,0x416b0c0000000000,0x685a0c0000000000},//46 + //mmm type groups + {0xec4b000000000000,0xec4b000000000000,0xec4b000000000000,0xec4b000000000000,0xec4b000000000000,0xec4b000000000000},//47 + {0x5744650000000000,0x5744650000000000,0x5744650000000000,0x5744650000000000,0x5744650000000000,0x5744650000000000},//48 + {0xc26b000000000000,0xc26b000000000000,0xec47000000000000,0xec47000000000000,0xcb6a000000000000,0xcb6a000000000000},//49 + {0x4566580000000000,0x4566580000000000,0x5b48690000000000,0x5b48690000000000,0x5967460000000000,0x5967460000000000},//50 + //only verified to here + {0xe84b000000000000,0xec49000000000000,0xc942000000000000,0xcb41000000000000,0xec41000000000000,0xe842000000000000},//51 + {0xda44000000000000,0xdc46000000000000,0xea57000000000000,0xd86b000000000000,0xd84a000000000000,0xc757000000000000},//52 + {0xec59000000000000,0xcb5b000000000000,0xcb66000000000000,0xc267000000000000,0xc248000000000000,0xec45000000000000},//53 + {0xda67000000000000,0xdc6b000000000000,0xc55a000000000000,0xc759000000000000,0xea5b000000000000,0xc766000000000000},//54 + {0xc245000000000000,0xc245000000000000,0xec5b000000000000,0xec5b000000000000,0xcb59000000000000,0xcb59000000000000},//55 + {0xd867000000000000,0xd867000000000000,0xc559000000000000,0xc559000000000000,0xe65b000000000000,0xe65b000000000000},//56 + {0xc16a000000000000,0xc147000000000000,0xe859000000000000,0xe845000000000000,0xc95a000000000000,0xc95b000000000000},//57 + {0xc244000000000000,0xc244000000000000,0xec57000000000000,0xec57000000000000,0xcb57000000000000,0xcb57000000000000},//58 + {0x6c4b580000000000,0x6c4b580000000000,0x4b42690000000000,0x4b42690000000000,0x6c42460000000000,0x6c42460000000000},//59 + {0xe657000000000000,0xc557000000000000,0xda48000000000000,0xc75b000000000000,0xd96a000000000000,0xdc67000000000000},//60 + {0xd966000000000000,0xc55b000000000000,0xd966000000000000,0xc55b000000000000,0xd966000000000000,0xc55b000000000000},//61 + {0xc959000000000000,0xe85b000000000000,0xc166000000000000,0xc957000000000000,0xe857000000000000,0xc145000000000000},//62 + {0xec4a0d0000000000,0xcb6b0d0000000000,0xe8105a0000000000,0xe810470000000000,0xc90e6a0000000000,0xec0e5c0000000000},//63 + {0xec59480d00000000,0xcb59670d00000000,0xcb59106700000000,0xc245106700000000,0xc2450e4800000000,0xec450e4800000000},//64 + {0xec0d420000000000,0xec0d420000000000,0xec0d420000000000,0xec0d420000000000,0xec0d420000000000,0xec0d420000000000},//65 + {0xc20d4a0000000000,0xc20d4a0000000000,0xec47100000000000,0xec47100000000000,0xcb6a0e0000000000,0xcb6a0e0000000000},//66 + {0xec495a0d00000000,0xe84b5a0d00000000,0xcb416a1000000000,0xc9426a1000000000,0xe842470e00000000,0xec41470e00000000},//67 + {0x595b0d6748000000,0x595b0d6748000000,0x4559666710000000,0x4559666710000000,0x45665b480e000000,0x45665b480e000000},//68 + {0xec4b100e00000000,0xec4b100e00000000,0xec4b100e00000000,0xec4b100e00000000,0xec4b100e00000000,0xec4b100e00000000},//69 + {0x6d436e0e10000000,0x6d436e0e10000000,0x6d436e0e10000000,0x6d436e0e10000000,0x6d436e0e10000000,0x6d436e0e10000000},//70 + {0xec0c420000000000,0xec0c420000000000,0xec0c420000000000,0xec0c420000000000,0xec0c420000000000,0xec0c420000000000},//71 + {0xc20c660000000000,0xc20c660000000000,0xec0c480000000000,0xec0c480000000000,0xcb0c670000000000,0xcb0c670000000000},//72 + {0xd90c480000000000,0xc50c670000000000,0xd90c480000000000,0xc50c670000000000,0xd90c480000000000,0xc50c670000000000},//73 + {0xec49590000000000,0xe84b5b0000000000,0xcb41660000000000,0xc942670000000000,0xe842480000000000,0xec41450000000000},//74 + }; + + //@brief : compress an EMsoft generator string into a 64 bit integer + //@param gen: 40 character EMsoft generator string to compress + //@return : uint64_t representation of generator string + uint64_t encode(std::string gen) { + //sanity check input + if(40 != gen.size()) throw std::runtime_error("invalid generator string (must be 40 characters long)"); + if(!('0' == gen[0] || '1' == gen[0])) throw std::runtime_error("invalid generator string (first character must be '0' or '1')"); + if(!std::isdigit(gen[1])) throw std::runtime_error("invalid generator string (second character must be a number)"); + + //now loop over 4 character substrings accumulating index + const size_t num = gen[1] - '0';//C++ guarentees 0-9 are contigous + uint64_t encoded = 0; + for(size_t i = 0; i < num; i++) { + std::string sub(gen.cbegin() + 2 + i * 4, gen.cbegin() + 6 + i * 4);//break out 4 character substring + const uint64_t idx = std::distance(GenLut, std::find_if(GenLut, GenLut + NumBlocks, [&sub](const char* str) {return sub == str;}));//get substring index + if(idx == NumBlocks) throw std::runtime_error("block `" + sub + "' not found in library");//make sure it was actually found + encoded |= idx << (56 - 8 * i);//accumulate indices + } + + //save inversion flag and return + if('1' == gen[0]) encoded |= 0x8000000000000000; + return encoded; + } + + //@brief : decompress an EMsoft generator string from a 64 bit integer + //@param enc: compressed generator string + //@return : EMsoft generator string + std::string decode(uint64_t enc) { + //build empty string and set inversion flag + std::string gen(40, ' '); + gen[0] = ((0x8000000000000000 & enc) == 0x8000000000000000) ? '1' : '0'; + + //build up generator string + for(int i = 7; i >= 0; i--) { + uint_fast8_t idx = 0x7F & enc;//extract index + std::copy(GenLut[idx], GenLut[idx]+4, (char*)gen.data() + 2 + 4 * i);//copy 4 character sequence into output + if(' ' == gen[1] && 0 != idx) {//this is the first non-zero character + if('1' == GenLut[idx][0]) {//this is an alternate origin + gen[1] = '0' + i;//just save generator count + } else {//this is the first empty block + gen[2+4*i+4] = '0';//0 terminate + gen[1] = '1' + i;//save generator count + } + } + enc >>= 8;//shift for next byte + } + if(' ' == gen[1]) gen[1] = gen[2] = '0';//handle space group 1 + return gen; + } + + //@brief : build generator matricies from compressed EMsoft generator + //@param enc: compressed generator string + //@param alt: should the alternate origin be used + //@return : 4x4 generator matricies (empty if alt = true but there is only 1 origin) + std::vector gen_from_enc(uint64_t enc, const bool alt) { + const bool hasInv = (0x8000000000000000 & enc) == 0x8000000000000000; + uint_fast8_t bytes[8]; + for(size_t i = 0; i < 8; i++) { + bytes[7-i] = 0x7F & enc;//get last byte + enc >>= 8;//shift over 1 byte + } + + //seed generators with identity (+inversion if needed) + std::vector gen(1, xtal::GenPos::Identity());//vector of generators + if(hasInv) gen.push_back(xtal::GenPos::Inversion()); + + //convert bytes to strings and count valid number + int8_t ori[3] = {0,0,0};//origin shift + for(int i = 0; i < 8; i++) {//up to 8 generators + xtal::GenPos p = xtal::GenPos::Identity(); + if(0x00 == bytes[i]) break;//we've used all the valid generators + if('1' == GenLut[bytes[i]][0]) {//there is an alternate origin to parse + p.fromEMsoft(GenLut[bytes[i]]); + p.getTrans(ori); + } else {//there is a regular element to parse + p.fromEMsoft(GenLut[bytes[i]]);//parse + gen.push_back(p); + } + } + + //update origin if needed and return + if(alt) { + if(0 == ori[0] && 0 == ori[1] && 0 == ori[2]) gen.clear();//there is no alternate origin + for(xtal::GenPos& p : gen) p = p.shiftOrigin(ori); + } + return gen; + } + + //@brief : build generator matricies for a space group + //@param sg : space group number [1,230] + //@param alt: should an alternate setting be selected (rhombohedral instead of hex or origin choice 2 instead of 1 as appropriate) + //@return : 4x4 generator matricies (empty if alt = true but there is only 1 setting) + std::vector gen_from_num(size_t sg, const bool alt) { + if(sg < 1 || sg > 230) throw std::runtime_error("space group number must be [1,230]"); + + //update index for rhombohedral settings if needed + bool useOri2 = false; + if(alt) { + switch(sg) { + case 146: sg = 231; break; + case 148: sg = 232; break; + case 155: sg = 233; break; + case 160: sg = 234; break; + case 161: sg = 235; break; + case 166: sg = 236; break; + case 167: sg = 237; break; + default: useOri2 = alt; + } + } + return gen_from_enc(SGLut[sg-1], useOri2); + } + + //@brief : build generator matricies for an extended monoclinic space group + //@param sg : space group number [3,15] + //@param cell: cell choice [1,3] + //@param axs : unique axis {"b", "-b", "c", "-c", "a", or "-a"} + //@return : 4x4 generator matricies (empty if alt = true but there is only 1 setting) + std::vector mono_gen_from_num(size_t sg, size_t cell, std::string axs) { + if(sg < 3 || sg > 15) throw std::runtime_error("monoclinic space group number must be [3,15]"); + if(cell < 1 || cell > 3 ) throw std::runtime_error("monoclinic cell must be [1,3]"); + uint64_t enc = 0; + if (axs == "b") enc = MonoLut[sg-3][0][cell-1]; + else if(axs == "-b") enc = MonoLut[sg-3][1][cell-1]; + else if(axs == "c") enc = MonoLut[sg-3][2][cell-1]; + else if(axs == "-c") enc = MonoLut[sg-3][3][cell-1]; + else if(axs == "a") enc = MonoLut[sg-3][4][cell-1]; + else if(axs == "-a") enc = MonoLut[sg-3][5][cell-1]; + else throw std::runtime_error("monoclinic axis must be {b, -b, c, -c, a, -a}"); + return gen_from_enc(enc << 32); + } + + //@brief : build generator matricies for an extended orthorhombic space group + //@param sg : space group number [16,74] + //@param axs : axis configuration {"abc", "bac", "cab", "cba", "bca", or "acb"} + //@return : 4x4 generator matricies (empty if alt = true but there is only 1 setting) + //@warning : unverified for space groups [51,74] and currently origin 1 only + std::vector ortho_gen_from_num(size_t sg, std::string axs) { + if(sg < 16 || sg > 74) throw std::runtime_error("orthorhombic space group number must be [3,15]"); + uint64_t enc = 0; + if (axs == "abc") enc = OrthoLut[sg-16][0]; + else if(axs == "bac") enc = OrthoLut[sg-16][1]; + else if(axs == "cab") enc = OrthoLut[sg-16][2]; + else if(axs == "cba") enc = OrthoLut[sg-16][3]; + else if(axs == "bca") enc = OrthoLut[sg-16][4]; + else if(axs == "acb") enc = OrthoLut[sg-16][5]; + else throw std::runtime_error("orthorhombic axis must be {abc, bac, cab, cba, bca, acb}"); + return gen_from_enc(enc); + } +} + + +#endif//_emgen_h_ \ No newline at end of file diff --git a/test/xtal/hm.cpp b/test/xtal/hm.cpp new file mode 100644 index 0000000..81f3230 --- /dev/null +++ b/test/xtal/hm.cpp @@ -0,0 +1,334 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +//////////////////////////////////////////////////////////////////////// +// test program for functions in include/sht/wigner.hpp // +//////////////////////////////////////////////////////////////////////// + + +#include + +namespace xtal { + //@brief : test if the Hermann-Maguin name class works + //@param os: location to write status + //@return : true/false if tests passed/failed + bool testHM(std::ostream& os); + + //@brief : test if the Hermann-Maguin name class parses names correctly + //@param os: location to write status + //@return : true/false if tests passed/failed + bool testParse(std::ostream& os); + + //@brief : test if the Hermann-Maguin name builds default generators correctly + //@param os: location to write status + //@return : true/false if tests passed/failed + bool testGen(std::ostream& os); + + //@brief : test if the Hermann-Maguin name build alternate generators correctly (rhomobohedral or origin choice 2) + //@param os: location to write status + //@return : true/false if tests passed/failed + bool testAlt(std::ostream& os); + + //@brief : test if the Hermann-Maguin name build extended monoclinic generators correctly + //@param os: location to write status + //@return : true/false if tests passed/failed + bool testMono(std::ostream& os); + + //@brief : test if the Hermann-Maguin name build extended orthorhombic generators correctly + //@param os: location to write status + //@return : true/false if tests passed/failed + bool testOrtho(std::ostream& os); + +} + +int main() { + return xtal::testHM(std::cout) ? EXIT_SUCCESS : EXIT_FAILURE; +} + +#include +#include + +#include "emsoft_gen.hpp" +#include "xtal/hm.hpp" + +namespace xtal { + + //@brief : test if the Hermann-Maguin name class works + //@param os: location to write status + //@return : true/false if tests passed/failed + bool testHM(std::ostream& os) { + return testParse(os) && + testGen (os) && + testAlt (os) && + testMono (os) && + testOrtho(os); + } + + //@brief : test if the Hermann-Maguin name class parses names correctly + //@param os: location to write status + //@return : true/false if tests passed/failed + bool testParse(std::ostream& os) { + //make sure we can build symbols from short names + for(size_t i = 0; i < 230; i++) { + HermannMaguin hmTable(i+1);//get name from number + HermannMaguin hmName; + hmTable.clearOrigin(); + hmName .fromString(emsoft::SGNames[i].c_str()); + if(!(hmName.shortSym() == hmTable.shortSym())) { + os << "didn't properly parse: " << emsoft::SGNames[i] << ":\n"; + os << "table :\t" << hmTable.shortSym().to_string() << '\t' << hmTable.to_string() << '\n'; + os << "parsed:\t" << hmName .shortSym().to_string() << '\t' << hmName .to_string() << '\n'; + return false; + } + } + return true; + } + + //@brief : test if the Hermann-Maguin name builds default generators correctly + //@param os: location to write status + //@return : true/false if tests passed/failed + bool testGen(std::ostream& os) { + os << "==============================\n"; + os << "checking default settings against EMsoft generator strings\n"; + os << "------------------------------\n"; + for(size_t i = 0; i < 230; i++) { + HermannMaguin hm(i+1);//get name from space group number + if(i < 99) os << (i < 9 ? " " : " "); + os << i+1 << ": " << hm.to_string() << '\t' << hm.shortSym().to_string() << '\n';//print full and short symbol + std::vector gen = hm.generators();//build generators from name + std::vector mats = GenPos::CloseSet(gen);//close set + + std::vector mats2 = GenPos::CloseSet(emsoft::gen_from_num(i+1));//get closed set of EMsoft generators + if(mats != mats2) {//make sure the generator sets match + mats = gen; + os << "old:\n"; + for(auto x : mats2) os << x.to_string() << '\n'; + os << "============\n"; + os << "new:\n"; + for(auto x : mats) os << x.to_string() << '\n'; + return false; + } + } + os << '\n'; + return true; + } + + //@brief : test if the Hermann-Maguin name build alternate generators correctly (rhomobohedral or origin choice 2) + //@param os: location to write status + //@return : true/false if tests passed/failed + bool testAlt(std::ostream& os) { + //test rhombohedral setting + os << "==============================\n"; + os << "testing rhombohedral settings against EMsoft generator strings\n"; + os << "------------------------------\n"; + for(size_t i = 0; i < 230; i++) {//loop over all groups + HermannMaguin hm(i+1);//get name from space group number + if('R' == hm.to_string().front() ) {//check if this is a rhombohedral group + os << i+1 << ": " << hm.to_string() << '\t' << hm.shortSym().to_string() << '\n';//print full and short symbol + std::vector gen = hm.generators(NULL, false);//build generators from name + std::vector mats = GenPos::CloseSet(gen);//close set + std::vector mats2 = GenPos::CloseSet(emsoft::gen_from_num(i+1, true));//get closed set of EMsoft generators + + //make sure the generator sets match + if(mats != mats2) { + os << "old:\n"; + for(auto x : mats2) os << x.to_string() << '\n'; + os << "============\n"; + os << "new:\n"; + for(auto x : mats) os << x.to_string() << '\n'; + return false; + } + } + } + os << '\n'; + + //test origin choice 2 + os << "==============================\n"; + os << "testing origin choice 2 against EMsoft generator strings\n"; + os << "------------------------------\n"; + for(size_t i = 0; i < 230; i++) {//loop over all groups + try {//try to build with alternate origin + HermannMaguin hm; + hm.fromNumber(i+1, true);//get name from space group number (throws if no alternate origin) + if(i < 99) os << (i+1 < 9 ? " " : " "); + os << i+1 << ": " << hm.to_string() << '\t' << hm.shortSym().to_string() << '\n';//print full and short symbol + std::vector gen = hm.generators();//build generators from name + std::vector mats = GenPos::CloseSet(gen);//close set + std::vector mats2 = GenPos::CloseSet(emsoft::gen_from_num(i+1, true));//Generator(i).closeSet(true);//get closed set of EMsoft generators + + if(mats != mats2) { + os << "old:\n"; + for(auto x : mats2) os << x.to_string() << '\n'; + // for(auto x : mats2) os << x.to_wyckoff() << '\n'; + os << "============\n"; + os << "new:\n"; + for(auto x : mats) os << x.to_string() << '\n'; + return false; + } + } catch(...) { + //only 1 setting + } + } + os << '\n'; + return true; + } + + //@brief : test if the Hermann-Maguin name build extended monoclinic generators correctly + //@param os: location to write status + //@return : true/false if tests passed/failed + bool testMono(std::ostream& os) { + //test for monoclinic settings + os << "==============================\n"; + os << "testing extended monoclinic symbols\n"; + os << "------------------------------\n"; + const std::string abc[6] = {"b","-b","c","-c","a","-a"}; + os << " # : "; + for(size_t i = 0; i < 6; i++) os << "\t " << abc[i] << " "; + os << '\n'; + for(size_t i = 3; i <= 15; i++) { + HermannMaguin hm(i);//get space group + //build table of 6 alternate settings in standard order + HermannMaguin extMono[3][6]; + for(size_t j = 0; j < 3; j++) { + for(size_t k = 0; k < 6; k++) { + extMono[j][k] = hm.changeMonoCell(j+1, abc[k]); + std::vector mats2 = GenPos::CloseSet(emsoft::mono_gen_from_num(i, j+1, abc[k])); + std::vector gen = extMono[j][k].generators();//build generators from name + std::vector mats = GenPos::CloseSet(gen);//close set + if(mats != mats2) { + os << "extended monoclinic inconsistent with table\n"; + return false; + } + } + } + + //determine if this group has multiple cells + bool hasCells = false; + for(size_t k = 0; k < 6; k++) { + if(extMono[0][k] != extMono[1][k] || extMono[1][k] != extMono[2][k]) { + hasCells = true; + break; + } + } + if(i < 99) os << (i < 9 ? " " : " "); + + //print alternate settings + if(hasCells) { + os << i << ":\n"; + for(size_t j = 0; j < 3; j++) { + os << " cell " << j+1; + for(size_t k = 0; k < 6; k++) os << " \t" << extMono[j][k].shortSym().to_string(); + os << '\n'; + } + os << '\n'; + } else { + os << i << ": "; + for(size_t k = 0; k < 6; k++) os << " \t" << extMono[0][k].shortSym().to_string(); + os << '\n'; + } + + + //now make sure round trip conversions are self consistent + for(size_t jj = 0; jj < 3; jj++) { + for(size_t kk = 0; kk < 6; kk++) { + for(size_t j = 0; j < 3; j++) { + for(size_t k = 0; k < 6; k++) { + HermannMaguin ext = extMono[jj][kk].changeMonoCell(j+1, abc[k]); + if(!(ext == extMono[j][k])) throw std::runtime_error("extended monoclinic conversions inconsistent"); + + } + } + } + } + + //TODO: check that we can produce extended symbols via transformation matrix + + + //TODO: check alternate symbols (e.g. C1a1 instead of C1m1 for 8) + } + // os << '\n'; + return true; + } + + //@brief : test if the Hermann-Maguin name build extended orthorhombic generators correctly + //@param os: location to write status + //@return : true/false if tests passed/failed + bool testOrtho(std::ostream& os) { + //test alternate orthorhombic settings + os << "==============================\n"; + os << "testing extended orthorhombic symbols\n"; + os << "------------------------------\n"; + const std::string abc[6] = {"abc","bac","cab","cba","bca","acb"}; + os << " # :"; + for(size_t i = 0; i < 6; i++) os << "\t " << abc[i] << " "; + os << '\n'; + for(size_t i = 16; i <= 74; i++) {//loop over orhtorhombic groups printing extended symbols + HermannMaguin hm(i);//get space group + + //build table of 6 alternate settings in standard order + HermannMaguin extOrtho[6]; + for(size_t j = 0; j < 6; j++) { + extOrtho[j] = hm.changeOrthoAxis(abc[j]); + std::vector mats2 = GenPos::CloseSet(emsoft::ortho_gen_from_num(i, abc[j])); + std::vector gen = extOrtho[j].generators();//build generators from name + std::vector mats = GenPos::CloseSet(gen);//close set + if(mats != mats2) { + os << "extended orthorhombic inconsistent with table\n"; + return false; + } + } + + //print alternate symbols + if(i < 99) os << (i < 9 ? " " : " "); + os << i << ":"; + for(size_t j = 0; j < 6; j++) os << " \t" << extOrtho[j].shortSym().to_string(); + os << '\n'; + + //now make sure round trip conversions are self consistent + for(size_t j = 0; j < 6; j++) { + for(size_t k = 0; k < 6; k++) { + HermannMaguin extK = extOrtho[j].changeOrthoAxis(abc[k]);//get round trip conversion + if(!(extK == extOrtho[k])) throw std::runtime_error("extended orthorhombic conversions inconsistent"); + } + } + + //TODO: check that we can produce extended symbols via transformation matrix + + //TODO: check alternate symbols + } + os << '\n'; + return true; + } + +} diff --git a/test/xtal/position.cpp b/test/xtal/position.cpp new file mode 100644 index 0000000..ea14d79 --- /dev/null +++ b/test/xtal/position.cpp @@ -0,0 +1,366 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +//////////////////////////////////////////////////////////////////////// +// test program for functions in include/xtal/position.hpp // +//////////////////////////////////////////////////////////////////////// + +#include + +namespace xtal { + //@brief : run all general position and generator unit tests + //@param os: output stream to write error messages to + //@return : true/false if the self tests pass/fail + bool runTests(std::ostream& os); + + + //@brief : test all general position constructors (+ static factories) + //@param os: output stream to write error messages to + //@return : true/false if the self tests pass/fail + bool testBuild(std::ostream& os); + + //@brief : test all general position matrix operations + //@param os: output stream to write error messages to + //@return : true/false if the self tests pass/fail + bool testMat(std::ostream& os); + +} + + +int main() { + return xtal::runTests(std::cout) ? EXIT_SUCCESS : EXIT_FAILURE; +} + + +#include "xtal/position.hpp" + +namespace xtal { + //@brief : run all general position and generator unit tests + //@param os: output stream to write error messages to + //@return : true/false if the self tests pass/fail + bool runTests(std::ostream& os) { + + //make sure conversion between 3x3 matrix and 64 matrix table is self consistent + for(uint_fast8_t i = 0; i < 64; i++) { + uint8_t j = xtal::GenPos::Mat3ToIdx(xtal::GenPos::IdxToMat3(i)); + if(i != j) { + os << "self consistent matrix <==> index failed for " << i << "\n"; + return false; + } + } + + //make sure constructors and matrix operations work + if(!testBuild(os)) return false; + if(!testMat (os)) return false; + + //make sure EMsoft parsing is self consistent for generators + for(uint32_t i = 0; i < 64; i++) { + GenPos p(i), q; + char em[5] = " "; + auto trs = {0,4,6,8,12,16,18,20};//allowable translations + try { + //loop over all possible translations + for(int8_t x : trs) { + for(int8_t y : trs) { + for(int8_t z : trs) { + int8_t t[3] = {x, y, z}; + p.setTrans(t); + p.toEMsoft(em); + q.fromEMsoft(em); + if(p != q) { + os << "EMsoft conversions not self consistent\n"; + return false; + } + } + } + } + } catch (...) { + //not all possibilities can be converted + } + } + + //make sure EMsoft parsing is self consistent for origin shifts + { + auto trs = {0,15,18,21};//allowable translations + GenPos p = GenPos::Identity(); + GenPos q; + char em[5] = " "; + for(int8_t x : trs) { + for(int8_t y : trs) { + for(int8_t z : trs) { + int8_t t[3] = {x, y, z}; + p.setTrans(t); + p.toEMsoft(em, true); + q.fromEMsoft(em); + if(p != q) { + os << "EMsoft conversions not self consistent\n"; + return false; + } + } + } + } + } + + //if we made it this far all tests passed + os << "all tests passed\n"; + return true; + } + + //@brief : test all general position constructors (+ static factories) + //@param os: output stream to write error messages to + //@return : true/false if the self tests pass/fail + bool testBuild(std::ostream& os) { + //make sure default constructor works and check parameterless static constructors + { + GenPos p; + if(p != GenPos::Identity()) { + os << "default general position isn't identity\n"; + return false; + } + + int8_t mat[9] = {1, 0, 0, 0, 1, 0, 0, 0, 1}; + int8_t const * m = p.getMat3(); + if(!std::equal(m, m+9, mat)) { + os << "identity isn't identity\n"; + return false; + } + + p = GenPos::Inversion(); + m = p.getMat3(); + mat[0] = mat[4] = mat[8] = -1; + if(!std::equal(m, m+9, mat)) { + os << "inversion isn't inversion\n"; + return false; + } + } + + //make sure mirror + 2 fold constructors work + { + //build possible normals + int8_t normals[11][3] = { + { 1, 0, 0}, { 0, 1, 0}, { 0, 0, 1}, { 0,-1, 1}, + { 0, 1, 1}, { 1,-1, 0}, { 1, 1, 0}, {-1, 0, 1}, + { 1, 0, 1}, { 1, 2, 0}, { 2, 1, 0}, + }; + + //loop over normals constructing matrices + for(size_t i = 0; i < 11; i++) { + for(bool b : {true, false}) { + //build mirror and make sure it is correct + GenPos p = GenPos::Mirror(normals[i], b); + if(-2 != p.order() || !std::equal(normals[i], normals[i]+3, p.axis())) { + os << "Mirror() constructor wrong\n"; + return false; + } + + //build 2 fold and make sure it is correct + p = GenPos::Two(normals[i], b); + if( 2 != p.order() || !std::equal(normals[i], normals[i]+3, p.axis())) { + os << "Two() constructor wrong\n"; + return false; + } + } + } + } + + //make sure 3 fold constructor works + { + int8_t normals[4][3] = { + { 1, 1, 1}, + {-1, 1, 1}, + { 1,-1, 1}, + {-1,-1, 1}, + }; + + for(size_t i = 0; i < 4; i++) { + //test 3 @ n + GenPos p = GenPos::Three(normals[i], false); + if( 3 != p.order() || !std::equal(normals[i], normals[i]+3, p.axis())) { + os << "Three() constructor wrong\n"; + return false; + } + + //test -3 @ n + p = GenPos::Three(normals[i], true); + if(-3 != p.order() || !std::equal(normals[i], normals[i]+3, p.axis())) { + os << "Three() constructor wrong\n"; + return false; + } + } + } + + //make sure z rotation constructor works + { + int8_t norm[3] = {0,0,1}; + for(int n : {2, 3, 4, 6, -2, -3, -4, -6}) { + GenPos p = GenPos::Z(n); + if(n != p.order() || !std::equal(norm, norm+3, p.axis())) { + os << "Z(" << n << ") constructor wrong\n"; + return false; + } + } + //handle +/-1 specially since they have degenerate rotation axis + if(GenPos::Z(1) != GenPos::Identity() ||GenPos::Z(-1) != GenPos::Inversion()) { + os << "Z(+/-1) constructor wrong\n"; + return false; + } + } + + //if we made it this far all constructors work + return true; + + } + + //@brief : test all general position matrix operations + //@param os: output stream to write error messages to + //@return : true/false if the self tests pass/fail + bool testMat(std::ostream& os) { + //test set / get + { + //3x3 matrix part + GenPos p; + for(uint32_t i = 0; i < 64; i++) {//loop over possible 3x3 matrices + int8_t const * m = GenPos(i).getMat3();//get the 3x3 part + p.setMat3(m);//tell p to use that matrix part + int8_t const * n = GenPos(i).getMat3();//make the 3x3 part from p + if(!std::equal(m, m+9, n)) { + os << "set/getMat3() inconsistent\n"; + return false; + } + } + + //translation part + for(int8_t x = 0; x < 24; x++) { + for(int8_t y = 0; y < 24; y++) { + for(int8_t z = 0; z < 24; z++) { + //test fractional 24ths setting + int8_t t[3] = {x, y, z}; + p.setTrans(t); + if(!std::equal(t, t+3, p.getTrans())) { + os << "set/getTrans() inconsistent\n"; + return false; + } + + //test real setting + double q[3]; + double r[3] = {double(x)/24, double(y)/24, double(z)/24}; + p.removeTrans(); + p.setTransReal(r); + p.getTransReal(q); + if(!std::equal(t, t+3, p.getTrans()) || !std::equal(q, q+3, r)) { + os << "set/getTransReal() inconsistent\n"; + return false; + } + } + } + } + } + + //check property queries and order + for(uint32_t i = 0; i < 64; i++) {//loop over possible 3x3 matrices + GenPos p(i);//build matrix + GenPos q(p);//copy + const int order = p.order();//get rotational order + GenPos ident = GenPos::Identity();//most matrices^order should be identity + if(order < 0 && ( (-order) % 2) == 1) ident = GenPos::Inversion();//odd roto inversions should be inversion + for(int j = 1; j < std::abs(order); j++) q = q*p;//compute p^order + if(q != ident ) {//make sure we get the expected result + os << "mat^order != identity\n"; + return false; + } + + if(order * p.det() < 0) { + os << "sign(order) !== sign(det())\n"; + return false; + } + } + + //make sure that inverse works + for(uint32_t i = 0; i < 64; i++) {//loop over possible 3x3 matrices + int8_t t[3] = {3, 8, 18};//random translation + GenPos p(i);//build matrix from 3x3 part + p.setTrans(t);//set translation + p *= p.inverse(); + if(GenPos::Identity() != p) { + os << "p * p.inverse() != identity\n"; + return false; + } + } + + //make sure origin shift is equivalent to transforming with identity | t + for(uint32_t i = 0; i < 64; i++) {//loop over possible 3x3 matrices + //save translation + identity | t + int8_t t[3] = {3, 8, 18};//random translation + GenPos q = GenPos::Identity(); + q.setTrans(t); + + //check for equivalence + GenPos p(i); + int8_t t2[3] = {4, 12, 9};//different random translation + p.setTrans(t2); + if(p.shiftOrigin(t) != p.transform(q)) { + os << "shiftOrigin is not special case of transform\n"; + return false; + } + } + + //make sure that transform is invertible + for(uint32_t i = 1; i < 64; i++) {//loop over possible 3x3 matrices except for identity + //build a random matrix + int8_t t[3] = {3, 8, 18};//random translation + GenPos p(i);//build matrix from 3x3 part + p.setTrans(t);//set translation + + //compute like bounds (don't multiply cubic with hexagonal matrices) + const uint32_t start = i < 48 ? 1 : 48; + const uint32_t end = i < 48 ? 48 : 64; + for(uint32_t j = start; j < end; j++) {//loop over possible 3x3 matrices in the same group + //build another random matrix + int8_t t2[3] = {4, 12, 9};//different random translation + GenPos q(j); + q.setTrans(t2); + + //transform one by the other + GenPos r = p.transform(q); + if(p == r || p != r.transform(q.inverse())) {//make sure a transform was actually applied and that it was inverted + os << "transform is not reversible\n"; + return false; + } + } + } + + //if we made it this far all matrix operations work + return true; + } +} diff --git a/test/xtal/quaternion.cpp b/test/xtal/quaternion.cpp new file mode 100644 index 0000000..761583c --- /dev/null +++ b/test/xtal/quaternion.cpp @@ -0,0 +1,216 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// tests for xtal/quaternion.cpp + +//@note : currently no test for quat::less + +#include + +namespace xtal { + //@brief : quaternion unit tests + //@return: true / false if tests pass / fail + template bool testQuats(std::ostream& os); +} + +int main() { + std::ostream& os = std::cout; + return xtal::testQuats(os) && xtal::testQuats(os) ? EXIT_SUCCESS : EXIT_FAILURE; +} + +#include +#include +#include + +#include "xtal/quaternion.hpp" + +namespace xtal { + + + //@brief : quaternion tests + //@return: true / false if tests pass / fail + template bool testQuats(std::ostream& os) { + //make 2 random quats + Real qu[4] = {1, 2, -3, 4}; + Real qr[4] = {5, -6, 7, -8}; + Real qv[4]; + const Real eps = std::sqrt(std::numeric_limits::epsilon()); + + //check magnitude functions + Real v; + v = quat::norm2(qu); + if(v != Real(30)) { + os << "quat norm2 of (1, 2, -3, 4) != 30\n"; + return false; + } + v = std::fabs(quat::norm(qr) - std::sqrt(Real(174))); + if(v > eps) { + os << "quat norm of (5, -6, 7, -8) != sqrt(174)\n"; + return false; + } + + //check dot product + v = quat::dot(qu, qr); + if(v != Real(-60)) { + os << "quat dot of (1, 2, -3, 4), (5, -6, 7, -8) != 60\n"; + return false; + } + + //check comparison + if(quat::equal(qu, qr) || !quat::equal(qu, qu) || !quat::equal(qr, qr)) { + os << "quat::equal failed\n"; + return false; + } + + //check element wise functions + quat::scalarAdd(qu, Real(-5), qv); + if(Real(-4) != qv[0] || Real(-3) != qv[1] || Real(-8) != qv[2] || Real(-1) != qv[3]) { + os << "quat::scalarAdd failed\n"; + return false; + } + quat::scalarSub(qv, Real(-5), qv); + if(!quat::equal(qu, qv)) { + os << "quat::scalarSub failed\n"; + return false; + + } + quat::scalarDiv(qv, Real(-0.5), qv); + if(Real(-2) != qv[0] || Real(-4) != qv[1] || Real(6) != qv[2] || Real(-8) != qv[3]) { + os << "quat::scalarDiv failed\n"; + return false; + } + quat::scalarMul(qv, Real(-0.5), qv); + if(!quat::equal(qu, qv)) { + os << "quat::scalarMul failed\n"; + return false; + } + + //check addition and subtraction + quat::add(qu, qr, qv); + if(Real(6) != qv[0] || Real(-4) != qv[1] || Real(4) != qv[2] || Real(-4) != qv[3]) { + os << "quat::add failed\n"; + return false; + } + quat::sub(qv, qr, qv); + if(!quat::equal(qu, qv)) { + os << "quat::sub failed\n"; + return false; + } + + //check simple urnary ops + quat::cAbs(qu, qv); + if(Real(1) != qv[0] || Real(2) != qv[1] || Real(3) != qv[2] || Real(4) != qv[3]) { + os << "quat::cAbs failed\n"; + return false; + } + quat::conj(qu, qv); + if(Real(1) != qv[0] || Real(-2) != qv[1] || Real(3) != qv[2] || Real(-4) != qv[3]) { + os << "quat::conj failed\n"; + return false; + } + quat::neg(qv, qv); + qv[0] = -qv[0]; + if(!quat::equal(qu, qv)) { + os << "quat::neg failed\n"; + return false; + } + quat::conj(qu, qv); + quat::neg (qv, qv); + quat::expl(qv, qv); + if(quat::equal(qu, qv)) { + os << "quat::expl failed\n"; + return false; + } + + //check multiplication + quat::mul(qu, qr, qv); + if(Real(70) != qv[0] || Real(8) != qv[1] || Real(0) != qv[2] || Real(16) != qv[3]) { + os << "quat::mul failed\n"; + return false; + } + + //check inverse + quat::inv(qu, qv); + quat::mul(qu, qv, qv); + if(std::fabs(qv[0] - Real(1)) > eps) { + os << "quat::inv failed\n"; + return false; + } + quat::cAbs(qv, qv); + if(std::max(std::max(qv[1], qv[2]), qv[3]) > eps) { + os << "quat::inv failed\n"; + return false; + } + + //check division + quat::mul(qu, qr, qv);//(qu * qr) + quat::div(qv, qr, qv);//(qu * qr) + if(!quat::equal(qu, qv)) { + os << "quat::div failed\n"; + return false; + } + + //check normalization + quat::normalize(qu, qv); + v = std::fabs(quat::norm(qv) - Real(1)); + if(v > eps) { + os << "quat::normalize failed\n"; + return false; + } + + //check active vector rotation + qv[0] = Real(0.5);//120 degrees + v = std::sqrt(Real(56) / 3); + qv[1] = Real(-1 * pijk) / v; + qv[2] = Real( 2 * pijk) / v; + qv[3] = Real(-3 * pijk) / v; + //qv is now passive rotation of 120 degrees @ {-1, 2, -3} + //rotate {5, -6, 7} + const Real vr[3] = {//this should be the simplified result (reguardless of pijk) + Real( 11) / 7 - std::sqrt(Real( 6) / 7), + Real(-36) / 7 - std::sqrt(Real(24) / 7), + Real( 61) / 7 - std::sqrt(Real( 6) / 7), + }; + quat::rotateVector(qv, qr, qv); + v = std::max(std::fabs(qv[0] - vr[0]), std::max(std::fabs(qv[1] - vr[1]), std::fabs(qv[2] - vr[2]))); + if(v > eps) { + os << "quat::rotateVector failed\n"; + return false; + } + + //if we made it this far all tests pass + os << "all tests passed\n"; + return true; + } +} diff --git a/test/xtal/rotations.cpp b/test/xtal/rotations.cpp new file mode 100644 index 0000000..bf69a85 --- /dev/null +++ b/test/xtal/rotations.cpp @@ -0,0 +1,339 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019, William C. Lenthe * + * All rights reserved. * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace xtal { + //@brief : test rotation conversions for 2 and 3 way self consistency, e.g. for 2 way make sure eu2ro, ro2eu give back the input + //@param n : euler grid density + //@param do3: should 3 way tests be included (this can significantly increase computation time) + //@param os : location to write errors + //@return : true / false if tests pass / fail + template bool testRound(const size_t n, const bool do3, std::ostream& os); + + //@brief : run all rotation tests + //@return: true / false if tests pass / fail + template bool testRots(std::ostream& os); +} + +int main() { + std::ostream& os = std::cout; + return xtal::testRots(os) && xtal::testRots(os) ? EXIT_SUCCESS : EXIT_FAILURE; +} + +#include +#include +#include //transform +#include //iota + +#include "xtal/rotations.hpp" + +namespace xtal { + + //@brief : compute maximum of the element wise distance between 2 vectors + //@param a : first vector + //@param b : second vector + //@param len: length of vectors + //@return : L1 distance + template Real maxDelta(Real const * const a, Real const * const b, const size_t len) { + Real maxDist = 0; + for(size_t i = 0; i < len; i++) { + const Real dist = std::fabs(a[i] - b[i]); + if(dist > maxDist) maxDist = dist; + } + return maxDist; + } + + //@brief : maxDelta adapted for rodrigues vectors + //@param a : first vector + //@param b : second vector + //@param dum: dummy parameter to match signature to maxDelta + //@return : ~L1 distance + template Real roDelta(Real const * const a, Real const * const b, const size_t dum) { + const Real dW = std::fabs( std::fabs( std::atan(a[3]) ) - std::fabs( std::atan(b[3]) ) ) * 2;//angular difference + return std::max(maxDelta(a, b, 3), dW);//difference is max of angular and axis differences + } + + //@brief : maxDelta adapted for euler angles vectors + //@param a : first vector + //@param b : second vector + //@param dum: dummy parameter to match signature to maxDelta + //@return : ~L1 distance + template Real euDelta(Real const * const a, Real const * const b, const size_t dum) { + Real qa[4], qb[4]; + eu2qu(a, qa); + eu2qu(b, qb); + return maxDelta(qa, qb, 4); + } + + //@brief : write a vector to an ostream + //@param v : vector + //@param len: length of vector + //@param os : ostream to write to + //@return : os + template std::ostream& printVec(Real const * const v, const size_t len, std::ostream& os) { + os << "("; + os << v[0]; + for(size_t i = 1; i < len; i++) os << ", " << v[i]; + return os << ")"; + } + + //@brief : test rotation conversions for 2 and 3 way self consistency, e.g. for 2 way make sure eu2ro, ro2eu give back the input + //@param n : euler grid density + //@param do3: should 3 way tests be included (this can significantly increase computation time) + //@param os : location to write errors + //@return : true / false if tests pass / fail + template bool testRound(const size_t n, const bool do3, std::ostream& os) { + std::string names[7] = {"eu", "om", "ax", "ro", "qu", "ho", "cu"}; + size_t lens [7] = { 3, 9, 4, 4, 4, 3, 3 }; + + //build a table of conversion functions + typedef void (*conversionFunc)(Real const * const, Real * const);//signature for conversion function pointer + conversionFunc conversion[7][7] = { + // 2eu 2om 2ax 2ro 2qu 2ho 2cu + { NULL, &eu2om, &eu2ax, &eu2ro, &eu2qu, &eu2ho, &eu2cu},// eu2 + {&om2eu, NULL, &om2ax, &om2ro, &om2qu, &om2ho, &om2cu},// om2 + {&ax2eu, &ax2om, NULL, &ax2ro, &ax2qu, &ax2ho, &ax2cu},// ax2 + {&ro2eu, &ro2om, &ro2ax, NULL, &ro2qu, &ro2ho, &ro2cu},// ro2 + {&qu2eu, &qu2om, &qu2ax, &qu2ro, NULL, &qu2ho, &qu2cu},// qu2 + {&ho2eu, &ho2om, &ho2ax, &ho2ro, &ho2qu, NULL, &ho2cu},// ho2 + {&cu2eu, &cu2om, &cu2ax, &cu2ro, &cu2qu, &cu2ho, NULL} // cu2 + }; + + //build a table of comparison functions + typedef Real (*comparisonFunc)(Real const * const, Real const * const, const size_t);//signature for comparison function pointer + comparisonFunc comparison[7] = { + &euDelta , + &maxDelta, + &maxDelta, + &roDelta , + &maxDelta, + &maxDelta, + &maxDelta + }; + + //build an evenly spaced list of euler angles + const Real pi = static_cast(3.1415926535897932384626433832795L); + std::vector phi((n - 1) * 2 + 1);//[0,2pi] + std::iota(phi.begin(), phi.end(), Real(0)); + std::transform(phi.begin(), phi.end(), phi.begin(), [&pi, &n](const Real i){return pi * i / (n-1);}); + std::vector theta(phi.begin(), phi.begin() + n);////[0,pi] + + //2 way tests + Real maxDiff = Real(0); + size_t maxI = 0, maxJ = 0, maxK = 0, maxM = 0, maxN = 0, maxP = 0; + Real eu[3], x[9], y[9], z[9], final[9]; + for(size_t i = 0; i < 7; i++) { + for(size_t j = 0; j < 7; j++) { + if(i == j) continue; + for(size_t m = 0; m < phi.size(); m++) { + eu[0] = phi[m]; + for(size_t n = 0; n < theta.size(); n++) { + eu[1] = theta[n]; + for(size_t p = 0; p < phi.size(); p++) { + eu[2] = phi[p]; + + //get base orientation from eulers + if(i == 0) std::copy(eu, eu+3, x); + else conversion[0][i](eu, x); + + //do conversion + conversion[i][j](x, y);//x2y + conversion[j][i](y, final);//y2x + + //compute error + Real diff = comparison[i](x, final, lens[i]); + if(diff > maxDiff) { + maxDiff = diff; + maxI = i; + maxJ = j; + maxM = m; + maxN = n; + maxP = p; + } + } + } + } + } + } + + os << "max diff pairwise = " << names[maxI] << "2" << names[maxJ] << "-" << names[maxJ] << "2" << names[maxI] << ": " << maxDiff << "\n"; + + //select threshold to pass, I choose cube root due to trig operators + //cbrt(epsilon) is ~0.005 and 6e-6 for float/double respectively + const Real eps2 = std::cbrt(std::numeric_limits::epsilon()); + if(maxDiff > eps2) { + os << "outside limit (" << eps2 << ")\n"; + + //get worst euler angle + eu[0] = phi [maxM]; + eu[1] = theta[maxN]; + eu[2] = phi [maxP]; + + //convert to base orientation + if(maxI == 0) std::copy(eu, eu+3, x); + else conversion[0][maxI](eu, x); + + //do conversion + conversion[maxI][maxJ](x, y);//x2y + conversion[maxJ][maxI](y, final);//y2x + + os << "\teu = "; + printVec(eu , 3 , os) << '\n'; + os << "\t" << names[maxI] << " = "; + printVec(x , lens[maxI], os) << '\n'; + os << "\t" << names[maxJ] << " = "; + printVec(y , lens[maxJ], os) << '\n'; + os << "\t" << names[maxI] << " = "; + printVec(final, lens[maxI], os) << '\n'; + return false; + } + + if(do3) { + //3 way tests + maxDiff = Real(0); + maxI = maxJ = maxK = maxM = maxN = maxP = 0; + for(size_t i = 0; i < 7; i++) { + for(size_t j = 0; j < 7; j++) { + if(i == j) continue; + for(size_t k = 0; k < 7; k++) { + if(j == k || k == i) continue; + for(size_t m = 0; m < phi.size(); m++) { + eu[0] = phi[m]; + for(size_t n = 0; n < theta.size(); n++) { + eu[1] = theta[n]; + for(size_t p = 0; p < phi.size(); p++) { + eu[2] = phi[p]; + //get base orientation from eulers + if(i == 0) std::copy(eu, eu+3, x); + else conversion[0][i](eu, x); + + //do conversion + conversion[i][j](x, y);//x2y + conversion[j][k](y, z);//y2z + conversion[k][i](z, final);//z2x + + //compute error + Real diff = comparison[i](x, final, lens[i]); + if(diff > maxDiff) { + maxDiff = diff; + maxI = i; + maxJ = j; + maxK = k; + maxM = m; + maxN = n; + maxP = p; + } + } + } + } + } + } + } + + os << "max diff three way = " << names[maxI] << "2" << names[maxK] << "-" << names[maxK] << "2" << names[maxJ] << "-" << names[maxJ] << "2" << names[maxI] << ": " << maxDiff << "\n"; + + //select threshold to pass, I choose cube root due to trig operators + //cbrt(epsilon) is ~0.005 and 6e-6 for float/double respectively + const Real eps3 = std::cbrt(std::numeric_limits::epsilon()); + if(maxDiff > eps3) { + os << "outside limit (" << eps3 << ")\n"; + + //get worst euler angle + eu[0] = phi [maxM]; + eu[1] = theta[maxN]; + eu[2] = phi [maxP]; + + //convert to base orientation + if(maxI == 0) std::copy(eu, eu+3, x); + else conversion[0][maxI](eu, x); + + //do conversion + conversion[maxI][maxJ](x, y);//x2y + conversion[maxJ][maxK](y, z);//y2z + conversion[maxK][maxI](z, final);//z2x + + os << "\teu = "; + printVec(eu , 3 , os) << '\n'; + os << "\t" << names[maxI] << " = "; + printVec(x , lens[maxI], os) << '\n'; + os << "\t" << names[maxJ] << " = "; + printVec(y , lens[maxJ], os) << '\n'; + os << "\t" << names[maxK] << " = "; + printVec(z , lens[maxK], os) << '\n'; + os << "\t" << names[maxI] << " = "; + printVec(final, lens[maxI], os) << '\n'; + return false; + } + } + + //finally make sure that zyz vs zxz is correct + const Real epsZYZ = std::numeric_limits::epsilon() * 10; + for(size_t m = 0; m < phi.size(); m++) { + eu[0] = phi[m]; + for(size_t n = 0; n < theta.size(); n++) { + eu[1] = theta[n]; + for(size_t p = 0; p < phi.size(); p++) { + eu[2] = phi[p]; + Real zyz[3] = {eu[0] - pi / 2, eu[1], eu[2] + pi / 2}; + + //check zyz2qu + eu2qu (eu , x); + zyz2qu(zyz, y); + + Real diff = maxDelta(x, y, 4); + if(diff > epsZYZ) { + os << "zyz2qu inconsistent with eu2qu({alpha + pi/2, beta, gamma - pi/2}) for:\n"; + os << "\teu : " << eu[0] << ' ' << eu[1] << ' ' << eu[2] << '\n'; + os << "\teu : " << x[0] << ' ' << x[1] << ' ' << x[2] << ' ' << x[3] << '\n'; + os << "\teu : " << y[0] << ' ' << y[1] << ' ' << y[2] << ' ' << y[3] << '\n'; + os << "\tdelta: " << diff << '\n'; + return false; + } + + //check qu2zyz + qu2zyz(x, y); + y[0] += pi / 2; + y[2] -= pi / 2; + diff = euDelta(eu, y, 3); + if(diff > epsZYZ) { + os << "qu2zyz inconsistent with qu2eu(){alpha - pi/2, beta, gamma + pi/2}) for:\n"; + os << "\teu : " << eu[0] << ' ' << eu[1] << ' ' << eu[2] << '\n'; + os << "\teu : " << x[0] << ' ' << x[1] << ' ' << x[2] << ' ' << x[3] << '\n'; + os << "\teu : " << y[0] << ' ' << y[1] << ' ' << y[2] << ' ' << y[3] << '\n'; + os << "\tdelta: " << diff << '\n'; + return false; + } + } + } + } + + //if we made it this far everything passed + return true; + } + + //@brief : run all rotation tests + //@return: true / false if tests pass / fail + template bool testRots(std::ostream& os) { + return testRound(25, false, os) && //high grid density for 2 way + testRound(15, true , os); //lower grid density for 3 way + } +} diff --git a/test/xtal/symmetry.cpp b/test/xtal/symmetry.cpp new file mode 100644 index 0000000..6b83910 --- /dev/null +++ b/test/xtal/symmetry.cpp @@ -0,0 +1,932 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (c) 2019-2019, De Graef Group, Carnegie Mellon University * + * All rights reserved. * + * * + * Author: William C. Lenthe * + * * + * This package is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, check the Free Software Foundation * + * website: * + * * + * * + * Interested in a commercial license? Contact: * + * * + * Center for Technology Transfer and Enterprise Creation * + * 4615 Forbes Avenue, Suite 302 * + * Pittsburgh, PA 15213 * + * * + * phone. : 412.268.7393 * + * email : innovation@cmu.edu * + * website: https://www.cmu.edu/cttec/ * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + +//////////////////////////////////////////////////////////////////////// +// test program for functions in include/xtal/symmetry.hpp // +//////////////////////////////////////////////////////////////////////// + +#include + +namespace xtal { + + //@brief : run all symmetry unit tests + //@param os: output stream to write error messages to + //@return : true/false if the self tests pass/fail + template bool runTests(std::ostream& os); + + //@brief : test point group constructors + //@param os: output stream to write error messages to + //@return : true/false if the tests pass/fail + bool testBuild(std::ostream& os); + + //@brief : test point group symmetry against space groups + //@param os: output stream to write error messages to + //@return : true/false if the tests pass/fail + template bool testSgSym(std::ostream& os); + + //@brief : test point group relationships + //@param os: output stream to write error messages to + //@return : true/false if the tests pass/fail + template bool testRel(std::ostream& os); + + //@brief : test point group conversions + //@param os: output stream to write error messages to + //@return : true/false if the tests pass/fail + bool testConv(std::ostream& os); + + //@brief : test symmetry operations of rotational point groups + //@param os: output stream to write error messages to + //@return : true/false if the tests pass/fail + template bool testRot(std::ostream& os); +} + +int main() { + //select output stream + std::ostream& os = std::cout; + + // //run unit tests + const bool passed = xtal::runTests(os) + && xtal::runTests(os); + + //return result + return passed ? EXIT_SUCCESS : EXIT_FAILURE; +} + +#include +#include +#include +#include +#include +#include + +#include "xtal/symmetry.hpp" +#include "xtal/position.hpp" +#include "xtal/rotations.hpp" +#include "xtal/hm.hpp" + +//@brief : square lambert projection from unit square to unit hemisphere +//@param X: x coordinate in unit square (0,1) +//@param Y: y coordinate in unit square (0,1) +//@param x: location to write x coordinate on unit sphere +//@param y: location to write y coordinate on unit sphere +//@param z: location to write z coordinate on unit sphere +template +void squareToSphere(Real const& X, Real const& Y, Real& x, Real& y, Real& z) { + static const Real kPi_4 = Real(0.7853981633974483096156608458199);//pi/4 + const Real sX = Real(2) * X - 1;//[0,1] -> [-1, 1] + const Real sY = Real(2) * Y - 1;//[0,1] -> [-1, 1] + const Real aX = std::abs(sX); + const Real aY = std::abs(sY); + const Real vMax = std::max(aX, aY); + if(vMax <= std::numeric_limits::epsilon()) { + x = y = Real(0); + z = Real(1); + } else { + if(vMax > Real(1) + std::numeric_limits::epsilon()) throw std::runtime_error("point doesn't lie in square (0,0) -> (1,1)"); + if(aX <= aY) { + const Real q = sY * std::sqrt(Real(2) - sY * sY); + const Real qq = kPi_4 * sX / sY; + x = q * std::sin(qq); + y = q * std::cos(qq); + } else { + const Real q = sX * std::sqrt(Real(2) - sX * sX); + const Real qq = kPi_4 * sY / sX; + x = q * std::cos(qq); + y = q * std::sin(qq); + } + z = Real(1) - vMax * vMax; + const Real mag = std::sqrt(x*x + y*y + z*z); + x /= mag; y /= mag; z /= mag; + } +} + +namespace xtal { + //enumerate list of point group names + const std::vector names = { + "1", "-1", "121", "112", "1m1", "11m", "12/m1", "112/m", + "222", "mm2", "mmm", "4", "-4" , "4/m", "422", "4mm", + "-42m", "-4m2", "4/mmm", "3", "-3", "321", "312", "3m1", + "31m", "-3m1", "-31m", "6", "-6", "6/m", "622", "6mm", + "-62m", "-6m2", "6/mmm", "23", "m3", "432", "-43m", "m3m", + }; + + //@brief : get a list of all point groups + //@return: list of groups + std::vector getGroups() { + //build list of point groups + std::vector groups; + for(const std::string n : names) groups.push_back(PointGroup(n)); + groups.push_back(PointGroup::BuildOrtho45("222r")); + groups.push_back(PointGroup::BuildOrtho45("mm2r")); + groups.push_back(PointGroup::BuildOrtho45("mmmr")); + return groups; + } + + //@brief : test point group constructors + //@param os: output stream to write error messages to + //@return : true/false if the tests pass/fail + bool testBuild(std::ostream& os) { + os << "testing space group number construction\n"; + for(uint_fast8_t i = 1; i <= 230; i++) {//loop over space groups + //get generators and split into translation / no translation + std::vector gen = GenPos::CloseSet(HermannMaguin(i).generators(NULL, true));//get generators + std::set noTrn; + std::vector cen; + for(GenPos& p : gen) { + GenPos n = p;//copy position + n.removeTrans();//remove translation component + noTrn.insert(n);//save translation free position + if(p.hasTranslation() && GenPos::Identity() == n) cen.push_back(p);//save any identity translations + } + + //get the point group for this number and then the primitive space group + PointGroup pg(i); + size_t sg = pg.symmorphic(); + if(0 == sg) { + os << "inconsistent symmorphic() for PointGroup(" << i << ")\n"; + return false; + } + + //make sure primitive group matches symmorphic(P) + std::vector genP = GenPos::CloseSet(HermannMaguin(sg).generators(NULL, true));//get generators + std::vector genRed(noTrn.begin(), noTrn.end());//convert translation reduced operators to vector + if(genP != genRed) { + os << "inconsistent symmorphic() generators for PointGroup(" << i << ")\n"; + return false; + } + } + + //test by name construction + os << "testing name construction\n"; + for(const std::string n : names) { + if(PointGroup(n).name() != n) { + os << "PointGroup(str).name() != str for str == " << n << '\n'; + return false; + } + } + + //test special orthorhombic constructor + os << "testing special construction\n"; + for(std::string n : {"222r", "mm2r", "mmmr"}) { + if(PointGroup::BuildOrtho45(n).name() != n) { + os << "BuildOrtho45(str).name() != str for str == " << n << '\n'; + return false; + } + } + + //if we made it this far everything passed + return true; + } + + //@brief : test point symmetry against space groups + //@param os: output stream to write error messages to + //@return : true/false if the tests pass/fail + template bool testSgSym(std::ostream& os) { + os << "testing that symmorphic() is correct\n"; + std::vector groups = getGroups(); + for(const PointGroup& pg : groups) {//loop over point groups + for(char c : {'P', 'A', 'B', 'C', 'F', 'I', 'R'}) {//loop over lattice centering types + uint_fast8_t sg = pg.symmorphic(c);//get symmorphic group for centering + if(sg != 0 && NULL == pg.symmorphicTrns()) {//there is a corresponding space group + HermannMaguin hm(sg);//get the space group + std::string name = hm.shortSym().to_string();//get the space group name + if(name.front() != c) {//make sure centering was correct + os << "symmorphic space group for point group " << pg.name() << " with " << c << " centering (" << sg << "): " << name << '\n'; + return false; + } + } + } + } + + os << "testing symmetry operations against primitive space group general positions\n"; + std::set rotSg, laueSg; + for(const PointGroup& pg : groups) {//loop over point groups + //check if we have a matching primitive group to work with + uint_fast8_t sg = pg.symmorphic('P'); + if(0 == sg) { + os << "no primitive space group for " << pg.name() << '\n'; + return false; + } + + //change cell if needed + HermannMaguin hm(sg); + bool hasStd = true;//has a corresponding ITA space group + std::string altMono = "b"; + if(NULL != pg.symmorphicTrns()) {//manually handle alternate monoclinic setting + if("112" == pg.name() || "11m" == pg.name() || "112/m" == pg.name()) hm = hm.changeMonoCell(1, "c"); + else hasStd = false;//222r type + } + + //make sure we have the right space group + std::string name = hm.shortSym().to_string();//get the space group name + if(!hasStd) name+= 'r';//handle 222r types + name.erase(std::remove(name.begin(), name.end(), ' '), name.end());//remove white space + if('P' != name[0] || 0 != name.compare(1, std::string::npos, pg.name())) { + os << "primitive space group " << name << " doesn't match point group " << pg.name() << '\n'; + return false; + } + + //space group matches, get the general positions + std::vector gen = GenPos::CloseSet(hm.generators());//we want hexagonal instead of rhombohedral generators where possible + if(gen.size() != pg.order()) { + os << pg.name() << " order doesn't match space group\n"; + return false; + } + + //check inversion flag + const bool sgInv = gen.end() != std::find(gen.begin(), gen.end(), GenPos::Inversion()); + if(sgInv != pg.inversion()) { + os << pg.name() << " inversion flag doesn't match space group\n"; + return false; + } + + //loop over general positions categorizing + bool hasMirZ = false, hasInv = false; + bool hasMirE = false, hasMirY = false; + int maxZ = 1; + int minZ = 1; + std::vector rots, mirs; + const int8_t eigVec[15][3] = {//these are all the possible eigenvectors for the 64 crystallographic 3x3 general position matrices + { 1, 0, 0}, { 0, 1, 0}, { 0, 0, 1}, { 0, 1, 1}, { 1, 0, 1}, + { 1, 1, 0}, { 0,-1, 1}, {-1, 0, 1}, { 1,-1, 0}, { 1, 1, 1}, + { 1,-1, 1}, {-1,-1, 1}, {-1, 1, 1}, { 2, 1, 0}, { 1, 2, 0}, + }; + int maxRot[15] = {1};//higher order rotation (or rotoinversion) detected at each axis + for(GenPos& p : gen) {//loop over general positions + //put into correct bin + const int order = p.order();//get rotational order + if (order > 0) rots.push_back(p);//rotation (or identity) + else if(order == -1) hasInv = true; + else if(order == -2) mirs.push_back(p); + else if(order < -2) {}//skip rotoinverters + else { + os << "unclassifiable general position in space group for " << pg.name() << '\n'; + return false; + } + + //determine rotation axis + size_t idx; + int8_t const * ax = p.axis(); + for(idx = 0; idx < 15; idx++) { + if(std::equal(ax, ax + 3, eigVec[idx])) break; + } + if(15 == idx) { + os << "unclassifiable general position axis in space group for " << pg.name() << '\n'; + return false; + } + + //accumulate maximum order in each direction + if(std::abs(order) > std::abs(maxRot[idx])) {//new largest magnitude + maxRot[idx] = order; + } else if(std::abs(order) == std::abs(maxRot[idx])) {//equal to largest magnitude + if(order % 2 == order > 0 ? 0 : 1) maxRot[idx] = order;//prefer proper inversions for even orders and rotoinversinos for odd orders + } + + //check for z elements specially + if(std::equal(eigVec[2], eigVec[2]+3, ax)) {//this is an operation about the z axis + minZ = std::min(order, minZ);//save largest z order + maxZ = std::max(order, maxZ);//save largest z order + if(-2 == p.order()) hasMirZ = true;//save mirror + } + + //check for x/y mirror specially + if(-2 == p.order()) {//mirror + if(0 == ax[2]) {//z == 0 + hasMirE = true;//there is at least 1 equatorial mirror + } + } + } + + //save purely rotational groups and laue groups + if(rots.size() == gen.size()) { + size_t rCount = rotSg.size(); + rotSg.insert(pg); + if(rCount == rotSg.size()) { + os << "duplicate purely rotational group\n"; + return false; + } + } else if(hasInv) { + size_t lCount = laueSg.size(); + laueSg.insert(pg); + if(lCount == laueSg.size()) { + os << "duplicate laue group\n"; + return false; + } + } + + //now that we've parsed out the general positions, check symmetry attributes + + //check inversion + if(hasInv != pg.inversion()) { + os << pg.name() << " inversion flag doesn't match space group\n"; + return false; + } + + //check z mirror + if(hasMirZ != pg.zMirror()) { + os << pg.name() << " z mirror flag doesn't match space group\n"; + return false; + } + + //check zRot + if(maxZ != pg.zRot()) { + os << pg.name() << " z rotational order doesn't match space group\n"; + return false; + } + + //check operation counts + if(mirs.size() != pg.numMirror()) { + os << pg.name() << " mirror count doesn't match space group\n"; + return false; + } + if(rots.size() != pg.numRotOps()) { + os << pg.name() << " rotation count doesn't match space group\n"; + return false; + } + + //check that enantiomorphism() is consistent with inversion() / numMirror() + if(pg.enantiomorphism() != ( mirs.empty() && !pg.inversion() && -4 != minZ ) ) { + os << pg.name() << " enantiomorphism inconsistent with inversion() / numMirror()\n"; + os << mirs.size() << ' ' << pg.inversion() << '\n'; + os << mirs.empty() << ' ' << !pg.inversion() << '\n'; + os << pg.enantiomorphism() << '\n'; + return false; + } + + //make sure rotational components are a closed set + if(GenPos::CloseSet(rots).size() != rots.size()) { + os << pg.name() << " rotational elements aren't a closed set\n"; + return false; + } + + //convert rotations to quaternions and check that they are the same + Real om[9]; + Quat qu; + Quat const * ops = (Quat const*) pg.rotOps(); + const bool hex = 0 == maxZ % 3; + const Real minDot = Real(1) - std::numeric_limits::epsilon() * 10;//threshold for 2 quaternions to be the same + for(GenPos& p : rots) { + //get 3x3 matrix as real type + if(hex) p.getMat3HexCart(om); + else std::copy(p.getMat3(), p.getMat3()+9, om); + + //transform if needed and convert to quaternion + if(!hasStd) { + Real const* a = pg.symmorphicTrns(); + const Real atOm[9] = {//a^T * om + a[0] * om[0] + a[3] * om[3] + a[6] * om[6], a[0] * om[1] + a[3] * om[4] + a[6] * om[7], a[0] * om[2] + a[3] * om[5] + a[6] * om[8], + a[1] * om[0] + a[4] * om[3] + a[7] * om[6], a[1] * om[1] + a[4] * om[4] + a[7] * om[7], a[1] * om[2] + a[4] * om[5] + a[7] * om[8], + a[2] * om[0] + a[5] * om[3] + a[8] * om[6], a[2] * om[1] + a[5] * om[4] + a[8] * om[7], a[2] * om[2] + a[5] * om[5] + a[8] * om[8], + }; + const Real atOmA[9] = {//a^T * om * a + atOm[0] * a[0] + atOm[1] * a[3] + atOm[2] * a[6], atOm[0] * a[1] + atOm[1] * a[4] + atOm[2] * a[7], atOm[0] * a[2] + atOm[1] * a[5] + atOm[2] * a[8], + atOm[3] * a[0] + atOm[4] * a[3] + atOm[5] * a[6], atOm[3] * a[1] + atOm[4] * a[4] + atOm[5] * a[7], atOm[3] * a[2] + atOm[4] * a[5] + atOm[5] * a[8], + atOm[6] * a[0] + atOm[7] * a[3] + atOm[8] * a[6], atOm[6] * a[1] + atOm[7] * a[4] + atOm[8] * a[7], atOm[6] * a[2] + atOm[7] * a[5] + atOm[8] * a[8], + }; + std::copy(atOmA, atOmA + 9, om); + } + om2qu(om, qu.data()); + + //loop over ops finding the best match + Real maxDot = 0; + for(size_t i = 0; i < rots.size(); i++) maxDot = std::max(maxDot, std::fabs(qu.dot(ops[i]))); + if(maxDot <= minDot) { + os << "failed to find all space group rotations in rotOps() for " << pg.name() << '\n'; + return false; + } + } + + //check that mirror planes are the same + Real const * norms = pg.mirrors(); + for(GenPos& p : mirs) { + //get mirror plane normal as unit vector + int8_t const * ax = p.axis();//get mirror plane normal + Real n[3] = {Real(ax[0]), Real(ax[1]), Real(ax[2])};//convert to real + if(hex) {//correct for hex frame if needed + n[0] = n[0] - n[1] / 2; + n[1] *= std::sqrt(Real(3)) / 2; + } + Real mag = std::sqrt(n[0]*n[0] + n[1]*n[1] + n[2]*n[2]);//get magnitude + n[0] /= mag; n[1] /= mag; n[2] /= mag;//normalize plane normal + + //transform if needed + if(!hasStd) { + Real const* a = pg.symmorphicTrns(); + const Real np[3] = {//a * n + a[0] * n[0] + a[1] * n[1] + a[2] * n[2], + a[3] * n[0] + a[4] * n[1] + a[5] * n[2], + a[6] * n[0] + a[7] * n[1] + a[8] * n[2], + }; + std::copy(np, np + 3, n); + } + if(std::fabs(n[1]) >= 0.99) hasMirY = true; + + //loop over ops finding the best match + Real maxDot = 0; + for(size_t i = 0; i < mirs.size(); i++) maxDot = std::max(maxDot, std::fabs(n[0]*norms[3*i+0] + n[1]*norms[3*i+1] + n[2]*norms[3*i+2])); + if(maxDot <= minDot) { + os << "failed to find all space group mirrors in rotOps() for " << pg.name() << '\n'; + return false; + } + } + + //check mmType + if(hasMirE) { + if(0 == pg.mmType()) {//z rotation + equatorial mirrors should have >0 mm type + os << pg.name() << " mmType doesn't match space group (is 0)\n"; + return false; + } else { + if(1 == pg.mmType() && hasMirY) { + //unrotated mm type + } else if(2 == pg.mmType() && !hasMirY) { + //rotated mm type + } else { + os << pg.name() << " mmType doesn't match space group (mismatch)\n"; + return false; + } + } + } else { + if(0 != pg.mmType()) {//no equatorial mirrors should have no mm type + os << pg.name() << " mmType doesn't match space group (not 0)\n"; + return false; + } + } + + //convert principal directions to real unit vectors + Real eigVecReal[15][3] = {//these are all the possible eigenvectors for the 64 crystallographic 3x3 general position matrices + { 1, 0, 0}, { 0, 1, 0}, { 0, 0, 1}, { 0, 1, 1}, { 1, 0, 1}, + { 1, 1, 0}, { 0,-1, 1}, {-1, 0, 1}, { 1,-1, 0}, { 1, 1, 1}, + { 1,-1, 1}, {-1,-1, 1}, {-1, 1, 1}, { 2, 1, 0}, { 1, 2, 0}, + }; + for(size_t i = 0; i < 15; i++) { + if(hex) {//correct for hex frame if needed + eigVecReal[i][0] = eigVecReal[i][0] - eigVecReal[i][1] / 2; + eigVecReal[i][1] *= std::sqrt(Real(3)) / 2; + } + Real mag = std::sqrt(eigVecReal[i][0]*eigVecReal[i][0] + + eigVecReal[i][1]*eigVecReal[i][1] + + eigVecReal[i][2]*eigVecReal[i][2]);//get magnitude + eigVecReal[i][0] /= mag; eigVecReal[i][1] /= mag; eigVecReal[i][2] /= mag;//normalize plane normal + + //transform if needed + if(!hasStd) { + Real const* a = pg.symmorphicTrns(); + const Real np[3] = {//a * n + a[0] * eigVecReal[i][0] + a[1] * eigVecReal[i][1] + a[2] * eigVecReal[i][2], + a[3] * eigVecReal[i][0] + a[4] * eigVecReal[i][1] + a[5] * eigVecReal[i][2], + a[6] * eigVecReal[i][0] + a[7] * eigVecReal[i][1] + a[8] * eigVecReal[i][2], + }; + std::copy(np, np + 3, eigVecReal[i]); + } + } + + //check that principal rotational symmetry operators are correct + size_t numOps = 0; + Real const * ax = pg.rotAxis();//get rotation axis + for(size_t i = 0; i < 15; i++) { + if( maxRot[i] > 1 || maxRot[i] < -2) {//there is a rotation to find + ++numOps; + Real maxDot = 0; + for(size_t j = 0; j < pg.numRotAxis(); j++) {//loop over rotation axis of point group + if(Real(maxRot[i]) != ax[4*j]) continue;//rotational order doesn't match + const Real dot = eigVecReal[i][0] * ax[4*j+1] + + eigVecReal[i][1] * ax[4*j+2] + + eigVecReal[i][2] * ax[4*j+3]; + maxDot = std::max( maxDot, std::fabs(dot) ); + } + if(maxDot < minDot) { + os << "rotAxis() doesn't match detected rotations for " << pg.name() << '\n'; + return false; + } + } + } + + //make sure the number of operators matches + if(numOps != pg.numRotAxis()) { + os << "numRotAxis() doesn't match detected rotations for " << pg.name() << '\n'; + return false; + } + + //loop over generators convering to 3x3 real matrix + std::vector< std::vector > realPos;//this is supper wasteful but thats ok + for(GenPos& p : gen) { + //get 3x3 matrix as real type + std::vector om(9); + if(hex) p.getMat3HexCart(om.data()); + else std::copy(p.getMat3(), p.getMat3()+9, om.data()); + + //transform if needed and convert to quaternion + if(!hasStd) { + Real const* a = pg.symmorphicTrns(); + const Real atOm[9] = {//a^T * om + a[0] * om[0] + a[3] * om[3] + a[6] * om[6], a[0] * om[1] + a[3] * om[4] + a[6] * om[7], a[0] * om[2] + a[3] * om[5] + a[6] * om[8], + a[1] * om[0] + a[4] * om[3] + a[7] * om[6], a[1] * om[1] + a[4] * om[4] + a[7] * om[7], a[1] * om[2] + a[4] * om[5] + a[7] * om[8], + a[2] * om[0] + a[5] * om[3] + a[8] * om[6], a[2] * om[1] + a[5] * om[4] + a[8] * om[7], a[2] * om[2] + a[5] * om[5] + a[8] * om[8], + }; + const Real atOmA[9] = {//a^T * om * a + atOm[0] * a[0] + atOm[1] * a[3] + atOm[2] * a[6], atOm[0] * a[1] + atOm[1] * a[4] + atOm[2] * a[7], atOm[0] * a[2] + atOm[1] * a[5] + atOm[2] * a[8], + atOm[3] * a[0] + atOm[4] * a[3] + atOm[5] * a[6], atOm[3] * a[1] + atOm[4] * a[4] + atOm[5] * a[7], atOm[3] * a[2] + atOm[4] * a[5] + atOm[5] * a[8], + atOm[6] * a[0] + atOm[7] * a[3] + atOm[8] * a[6], atOm[6] * a[1] + atOm[7] * a[4] + atOm[8] * a[7], atOm[6] * a[2] + atOm[7] * a[5] + atOm[8] * a[8], + }; + std::copy(atOmA, atOmA + 9, om.data()); + } + realPos.push_back(om); + } + + //loop over a grid of all unit directions doing some tests + std::vector lin(501); + for(int i = 0; i < lin.size(); i++) lin[i] = Real(i) / (lin.size() - 1);// [0,1] + size_t fsCount = 0; + for(Real& x : lin) { + for(Real& y : lin) { + //get the unit direction in the north hemisphere + Real nh[3]; + squareToSphere(x, y, nh[0], nh[1], nh[2]); + Real sh[3] = {nh[0], nh[1], -nh[2]}; + + //get fundamental sector direction and check if already inide + Real nhFs[3], shFs[3]; + const bool nhIn = pg.fsDir(nh, nhFs); + const bool shIn = pg.fsDir(sh, shFs); + if(nhIn) ++fsCount; + if(shIn) ++fsCount; + + //make sure reduction was a symmetric equivalent + Real maxDotNh = 0, maxDotSh = 0; + for(std::vector& om : realPos) {//loop over general positions + //compute om * nh + Real omNh[3] = { + om[0] * nh[0] + om[1] * nh[1] + om[2] * nh[2], + om[3] * nh[0] + om[4] * nh[1] + om[5] * nh[2], + om[6] * nh[0] + om[7] * nh[1] + om[8] * nh[2], + }; + + //compute om * sh + Real omSh[3] = { + om[0] * sh[0] + om[1] * sh[1] + om[2] * sh[2], + om[3] * sh[0] + om[4] * sh[1] + om[5] * sh[2], + om[6] * sh[0] + om[7] * sh[1] + om[8] * sh[2], + }; + + //check for best match + maxDotNh = std::max(maxDotNh, omNh[0] * nhFs[0] + omNh[1] * nhFs[1] + omNh[2] * nhFs[2]); + maxDotSh = std::max(maxDotSh, omSh[0] * shFs[0] + omSh[1] * shFs[1] + omSh[2] * shFs[2]); + } + if(maxDotNh < minDot || maxDotSh < minDot) { + os << "fundamental sector direction isn't genpos * n for " << pg.name() << '\n'; + return false; + } + } + } + + //make sure fraction of directions in fundamental sector matches order + const Real fsFrac = Real(lin.size() * lin.size() * 2) / fsCount; + if(std::round(fsFrac) != pg.order()) { + os << "fundmental sector fraction doesn't match order for " << pg.name() << '\n'; + os << fsFrac << ' ' << (int) pg.order() << '\n'; + return false; + } + } + + //this is a cludge until alternate settings is wrapped up + rotSg .insert(PointGroup("112")); + rotSg .insert(PointGroup::BuildOrtho45("222r")); + laueSg.insert(PointGroup("112/m")); + laueSg.insert(PointGroup::BuildOrtho45("mmmr")); + + //make sure rotational point group is actually purely rotational + for(const PointGroup& pg : groups) {//loop over point groups + if(pg.rotationGroup().numMirror() > 0 || pg.rotationGroup().inversion()) { + os << "rotation group of " << pg.name() << " isn't a purely rotation group\n"; + os << pg.rotationGroup().name() << '\n'; + return false; + } + if(1 != rotSg.count(pg.rotationGroup())) { + os << "rotation group of " << pg.name() << " not accumlated from space groups\n"; + os << pg.rotationGroup().name() << '\n'; + return false; + } + } + + //make sure the laue group is actualy inversion symmetric + for(const PointGroup& pg : groups) {//loop over point groups + if(!pg.laueGroup().inversion()) { + os << "laue group of " << pg.name() << " isn't a centrosymmetric\n"; + os << pg.laueGroup().name() << '\n'; + return false; + } + if(1 != laueSg.count(pg.laueGroup())) { + os << "laue group of " << pg.name() << " not accumlated from space groups\n"; + os << pg.laueGroup().name() << '\n'; + return false; + } + } + + //if we made it this far everything passed + return true; + } + + //@brief : test point group relationships + //@param os: output stream to write error messages to + //@return : true/false if the tests pass/fail + template bool testRel(std::ostream& os) { + os << "testing point group relations\n"; + std::vector groups = getGroups(); + + //make sure rotation point group is a subset of group rotations + for(const PointGroup& pg : groups) {//loop over point groups + //make sure number of operators is the same + size_t num = pg.numRotOps(); + if(pg.rotationGroup().numRotOps() != num) { + os << "rotation group of " << pg.name() << " has different rotation count\n"; + return false; + } + + //make sure actual operators are the same + Real const * rots = pg.rotOps(); + Real const * rotsR = pg.rotationGroup().rotOps(); + if(!std::equal(rots, rots + num * 4, rotsR)) { + os << "rotation group of " << pg.name() << " has different rotations\n"; + return false; + } + } + + //make sure group is a subset of the laue group + for(const PointGroup& pg : groups) {//loop over point groups + //sanity check number of operators + size_t numR = pg.numRotOps(); + size_t numM = pg.numMirror(); + size_t numRL = pg.laueGroup().numRotOps(); + size_t numML = pg.laueGroup().numMirror(); + if(numRL < numR || numML < numM) { + os << "laue group of " << pg.name() << " has fewer operators\n"; + return false; + } + + //make sure mirror planes are subset of laue group mirrors + Real const * mirs = pg.mirrors(); + Real const * mirsL = pg.laueGroup().mirrors(); + for(size_t i = 0; i < numM; i++) {//loop over mirrors + bool found = false; + for(size_t j = 0; j < numML; j++) {//loop over laue group mirrors looking for a match + if(std::equal(mirs + 3 * i, mirs + 3 * i + 3, mirsL + 3 * j)) {//we found a match + found = true;//flag + break;//stop looking + } + } + if(!found) {//check that the mirror was found + os << "laue group of " << pg.name() << " doesn't have all mirror planes\n"; + return false; + } + } + + //do the same for rotations + Real const * rots = pg.rotOps(); + Real const * rotsL = pg.laueGroup().rotOps(); + for(size_t i = 0; i < numR; i++) {//loop over rotations + bool found = false; + for(size_t j = 0; j < numRL; j++) {//loop over laue group rotations looking for a match + if(std::equal(rots + 4 * i, rots + 4 * i + 4, rotsL + 4 * j)) {//we found a match + found = true;//flag + break;//stop looking + } + } + if(!found) {//check that the mirror was found + os << "laue group of " << pg.name() << " doesn't have all rotations\n"; + return false; + } + } + } + + //check that laueName() is the same as laueGroup().name() + for(const PointGroup& pg : groups) {//loop over point groups + if(pg.laueName() != pg.laueGroup().laueName()) { + os << "laueName() of " << pg.name() << " != laueGroup().laueName()\n"; + return false; + } + } + + //if we made it this far everything passed + return true; + } + + //@brief : test point group conversions + //@param os: output stream to write error messages to + //@return : true/false if the tests pass/fail + bool testConv(std::ostream& os) { + os << "testing conversions\n"; + std::vector groups = { + PointGroup("-1" ), + PointGroup("12/m1"), + PointGroup("mmm" ), + PointGroup("4/m" ), + PointGroup("4/mmm"), + PointGroup("-3" ), + PointGroup("-3m1" ), + PointGroup("6/m" ), + PointGroup("6/mmm"), + PointGroup("m3" ), + PointGroup("m3m" ), + }; + + for(const PointGroup& pg : groups) {//loop over point groups + if(pg.laueGroup() != PointGroup::FromTSL(pg.tslNum())) { + os << pg.name() << " round trip TSL conversion isn't self consistent\n"; + return false; + } + + if(pg.laueGroup() != PointGroup::FromHKL(pg.hklNum())) { + os << pg.name() << " round trip HKL conversion isn't self consistent\n"; + return false; + } + } + + //if we made it this far everything passed + return true; + } + + + //@brief : test symmetry operations of rotational point groups + //@param os: output stream to write error messages to + //@return : true/false if the tests pass/fail + template bool testRot(std::ostream& os) { + //get the purely rotational groups + os << "testing rotataional group symmetry operations:\n"; + std::vector groups = getGroups(); + { + std::set rotGroups; + for(PointGroup& pg : groups) rotGroups.insert(pg.rotationGroup()); + groups.clear(); + groups.assign(rotGroups.begin(), rotGroups.end()); + } + + //grid the side length of the cubochoric cube + std::vector lin(151);//this needs to be pretty fine for FZ fraction to work out + for(int i = 0; i < lin.size(); i++) { + lin[i] = Real(i) / (lin.size() - 1) - Real(0.5);// [-0.5,0.5] + lin[i] *= Constants::cuA;//grid a side of the cubochoric cube + } + + //loop over a grid of all orientations doing some tests + std::vector inFz(groups.size(), 0); + const Real minDot = Real(1) - std::numeric_limits::epsilon() * 100;//how close do quat dots need to be for equality + for(Real& x : lin) { + os << "\t" << x << "/" << lin.back() << " \r"; + os.flush(); + for(Real& y : lin) { + for(Real& z : lin) { + //get cubochoric grid point and convert to ro + Real cu[3] = {x, y, z}; + Real ro[4]; + cu2ro(cu, ro); + + //count number of grid point in FZ + for(size_t i = 0; i < groups.size(); i++) { + //check if in fundamental zone + const bool inFzRo = groups[i].roInFz(ro); + if(inFzRo) ++inFz[i]; + + //get FZ quat + Real qu[4], quFz[4]; + ro2qu(ro, qu); + groups[i].fzQu(qu, quFz); + + //check that fzQu is consistent with roInFz + const Real dot = std::fabs(quat::dot(qu, quFz)); + const bool inFzQu = dot >= minDot; + if(inFzQu != inFzRo) { + os << "fzQu not consistent with roInFz for " << groups[i].name() << '\n'; + return false; + } + + //make sure we actually got an fz orientations + Real roFz[4]; + qu2ro(quFz, roFz);//convert to FZ + roFz[3] *= minDot;//allow for roundoff error (pull everything towards the origin a bit) + if(!groups[i].roInFz(roFz)) { + os << "fzQu didn't return a fundamental zone quaternion\n"; + return false; + } + } + } + } + } + os << '\n'; + + //make sure that the order is consistent with the FZ fraction + for(size_t i = 0; i < groups.size(); i++) { + const Real fzOrder = Real(lin.size() * lin.size() * lin.size()) / inFz[i]; + if(groups[i].order() != (int) std::round(fzOrder)) { + os << groups[i].name() << " order() != FZ fraction " << fzOrder << '\n'; + return false; + } + } + + //test disoQu and nearbyQu + Quat qu1, qu2, delta; + std::mt19937_64 gen(0); + const Real minW = std::cos(Constants::pi / 12);//minimum W for hexagonal disorientations = (1+sqrt(3)) / (2*sqrt(2)) + std::uniform_real_distribution distR(-1, 1); + std::uniform_int_distribution distI(0, 23); + std::vector< Quat const * > ops;//get operators once + std::vector< size_t > numOps;//get # operators once + for(PointGroup& pg : groups) { + ops.push_back((Quat const *) pg.rotOps()); + numOps.push_back(pg.numRotOps()); + } + for(size_t i = 0; i < 10000; i++) { + //generate a random quaternion + qu1.w = distR(gen); + qu1.x = distR(gen); + qu1.y = distR(gen); + qu1.z = distR(gen); + qu1 = qu1.normalize(); + qu1 = qu1.expl(); + + //generate another random quaternion with angle < 15 degrees (guaranteed to be a disorientation) + do { + delta.w = distR(gen); + if(std::fabs(delta.w) < minW) continue;// |w| can only get smaller when we normalize + delta.x = distR(gen); + delta.y = distR(gen); + delta.z = distR(gen); + delta = delta.normalize(); + } while( std::fabs(delta.w) <= minW ); + delta = delta.expl(); + qu2 = delta.conj() * qu1;//perturb first quaternion + + //now loop over point groups testing disoQu and nearbyQu + for(size_t j = 0; j < groups.size(); j++) { + //rotate qu1 and qu2 by random symmetry operators + Quat qu1P = ops[j][distI(gen) % numOps[j]] * qu1; + Quat qu2P = ops[j][distI(gen) % numOps[j]] * qu2; + + //compute disorientation and make sure it is correct + Quat diso; + groups[j].disoQu(qu1P.data(), qu2P.data(), diso.data()); + if(std::fabs(diso.w - delta.w) > 0.001) { + // if(std::fabs(diso.dot(delta)) < minDot) {//don't check dot product since axis may be inconsistent for some point groups + os << "disoQu failed to produce applied w for " << groups[j].name() << '\n'; + return false; + } + + //compute nearby qu and make sure it is correct + Quat near; + groups[j].nearbyQu(qu1.data(), qu2P.data(), near.data()); + if(std::fabs(near.dot(qu2)) < minDot) {//don't check dot product since axis may be inconsistent for some point groups + os << "nearbyQu failed to produce applied w for " << groups[j].name() << '\n'; + return false; + } + } + } + + //if we made it this far everything passed + return true; + } + + //@brief : run all symmetry unit tests + //@param os: output stream to write error messages to + //@return : true/false if the self tests pass/fail + template bool runTests(std::ostream& os) { + os << " * * * warning, the following functions are untested * * *\n"; + os << '\t' << "ipfColor\n";//need a good test for ipfColor (maybe a small gridding of orientation space for an unusual reference direction) + + return testBuild (os) && + testSgSym(os) && + testRel (os) && + testConv (os) && + testRot (os); + } +}