From a8d53959c6b3efd31d8726a612725253ab2c1441 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Fri, 11 Feb 2022 11:48:22 +0100 Subject: [PATCH 01/46] change build environment form make to meson --- Makefile | 50 - doc/doxygen/Doxyfile | 2658 ++++++++++++++++++++++++++++++++ doc/doxygen/Doxyfile.in | 2660 +++++++++++++++++++++++++++++++++ doc/doxygen/DoxygenLayout.xml | 194 +++ doc/doxygen/bootstrap.min.css | 6 + doc/doxygen/bootstrap.min.js | 7 + doc/doxygen/customdoxygen.css | 306 ++++ doc/doxygen/doxy-boot.js | 121 ++ doc/doxygen/footer.html | 26 + doc/doxygen/header.html | 47 + doc/doxygen/meson.build | 38 + include/meson.build | 13 + meson.build | 27 + meson_options.txt | 2 + test/cmp_icu/meson.build | 14 + test/cmp_icu/test_cmp_icu.c | 128 ++ test/cmp_tool/Makefile | 25 - test/cmp_tool/meson.build | 4 + test/meson.build | 33 + 19 files changed, 6284 insertions(+), 75 deletions(-) delete mode 100644 Makefile create mode 100644 doc/doxygen/Doxyfile create mode 100644 doc/doxygen/Doxyfile.in create mode 100644 doc/doxygen/DoxygenLayout.xml create mode 100644 doc/doxygen/bootstrap.min.css create mode 100644 doc/doxygen/bootstrap.min.js create mode 100644 doc/doxygen/customdoxygen.css create mode 100644 doc/doxygen/doxy-boot.js create mode 100644 doc/doxygen/footer.html create mode 100644 doc/doxygen/header.html create mode 100644 doc/doxygen/meson.build create mode 100644 include/meson.build create mode 100644 meson.build create mode 100644 meson_options.txt create mode 100644 test/cmp_icu/meson.build create mode 100644 test/cmp_icu/test_cmp_icu.c delete mode 100644 test/cmp_tool/Makefile create mode 100644 test/cmp_tool/meson.build create mode 100644 test/meson.build diff --git a/Makefile b/Makefile deleted file mode 100644 index 6038a87..0000000 --- a/Makefile +++ /dev/null @@ -1,50 +0,0 @@ -CC = gcc -SOURCEDIR = lib -INCLUDEDIR = include -BUILDDIR = ./ -CFLAGS := -Wall -Wextra -std=gnu99 -mno-ms-bitfields -pedantic -Wshadow \ - -Wunreachable-code #-Wdocumentation #-Wsign-conversion -RELCFLAGS := -O2 # Release flags -DBCFLAGS := -O0 -g3 -fsanitize=address -fsanitize=undefined #debug flags -COVFLAGS := -fprofile-arcs -ftest-coverage #coverage flags -CPPFLAGS := -I $(INCLUDEDIR) -LDFLAGS := -lm -SOURCES := cmp_tool.c \ - $(SOURCEDIR)/rmap.c \ - $(SOURCEDIR)/rdcu_ctrl.c \ - $(SOURCEDIR)/rdcu_cmd.c \ - $(SOURCEDIR)/rdcu_rmap.c \ - $(SOURCEDIR)/cmp_support.c \ - $(SOURCEDIR)/cmp_data_types.c \ - $(SOURCEDIR)/cmp_rdcu.c \ - $(SOURCEDIR)/cmp_icu.c \ - $(SOURCEDIR)/decmp.c \ - $(SOURCEDIR)/rdcu_pkt_to_file.c \ - $(SOURCEDIR)/cmp_guess.c \ - $(SOURCEDIR)/cmp_io.c \ - $(SOURCEDIR)/cmp_entity.c -TARGET := cmp_tool - -DEBUG?=1 -ifeq "$(shell expr $(DEBUG) \> 1)" "1" - CFLAGS += -DDEBUGLEVEL=$(DEBUG) -else - CFLAGS += -DDEBUGLEVEL=1 -endif - - -all: $(SOURCES) - $(CC) $(CPPFLAGS) $(CFLAGS) $(RELCFLAGS) $^ -o $(TARGET) $(LDFLAGS) - -debug: $(SOURCES) - $(CC) $(CPPFLAGS) $(CFLAGS) $(DBCFLAGS) $^ -o $(TARGET) $(LDFLAGS) - -integration: $(SOURCES) - $(CC) $(CPPFLAGS) $(CFLAGS) $(DBCFLAGS) $(COVFLAGS) $^ -o $(TARGET) $(LDFLAGS) - pytest test/cmp_tool/cmp_tool_integration_test.py - lcov --rc lcov_branch_coverage=1 --capture --directory ./ --output-file coverage.info - genhtml --rc lcov_branch_coverage=1 --branch-coverage coverage.info --output-directory out -#firefox out/index.html - -clean: - rm -rf $(TARGET) *.gcno *.gcda coverage.info out diff --git a/doc/doxygen/Doxyfile b/doc/doxygen/Doxyfile new file mode 100644 index 0000000..5bcc6da --- /dev/null +++ b/doc/doxygen/Doxyfile @@ -0,0 +1,2658 @@ +# Doxyfile 1.9.3 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "PLATO cmp_tool" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = 0.09 #TODO + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "PLATO Compression/Decompression Tool" + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = NO + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = YES + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = YES + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = YES + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# The default value is: system dependent. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if <section_label> ... \endif and \cond <section_label> +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = "/bin/sh -c 'git log --pretty=\"format:%ci, author:%aN <%aE>, commit:%h\" -1 \"${1}\" || echo no git'" + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = YES + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = ../ + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, +# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C +# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, +# *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.l \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# ANamespace::AClass, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# <filter> <input-file> +# +# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = YES + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = header.html + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = footer.html + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = customdoxygen.css + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = YES + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = YES + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/ + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see https://docs.mathjax.org/en/v2.7-latest/tex.html +# #tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use <access key> + S +# (what the <access key> is depends on the OS and browser, but it is typically +# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down +# key> to jump into the search results window, the results can be navigated +# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel +# the search. The filter options can be selected when the cursor is inside the +# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys> +# to select a filter and <Enter> or <escape> to activate or cancel the filter +# option. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +SEARCHENGINE = NO + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a web server instead of a web client using JavaScript. There +# are two flavors of web server based searching depending on the EXTERNAL_SEARCH +# setting. When disabled, doxygen will generate a PHP script for searching and +# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing +# and searching needs to be provided by external tools. See the section +# "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SERVER_BASED_SEARCH = NO + +# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP +# script for searching. Instead the search results are written to an XML file +# which needs to be processed by an external indexer. Doxygen will invoke an +# external search engine pointed to by the SEARCHENGINE_URL option to obtain the +# search results. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: +# https://xapian.org/). +# +# See the section "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH = NO + +# The SEARCHENGINE_URL should point to a search engine hosted by a web server +# which will return the search results when EXTERNAL_SEARCH is enabled. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: +# https://xapian.org/). See the section "External Indexing and Searching" for +# details. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHENGINE_URL = + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed +# search data is written to a file for indexing by an external tool. With the +# SEARCHDATA_FILE tag the name of this file can be specified. +# The default file is: searchdata.xml. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHDATA_FILE = searchdata.xml + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the +# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is +# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple +# projects and redirect the results back to the right project. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH_ID = + +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# projects other than the one defined by this configuration file, but that are +# all added to the same external search index. Each project needs to have a +# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of +# to a relative location where the documentation can be found. The format is: +# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. +# The default value is: YES. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: latex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. +# +# Note that when not enabling USE_PDFLATEX the default is latex when enabling +# USE_PDFLATEX the default is pdflatex and when in the later case latex is +# chosen this is overwritten by pdflatex. For specific output languages the +# default can have been set differently, this depends on the implementation of +# the output language. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate +# index for LaTeX. +# Note: This tag is used in the Makefile / make.bat. +# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file +# (.tex). +# The default file is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +MAKEINDEX_CMD_NAME = makeindex + +# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to +# generate index for LaTeX. In case there is no backslash (\) as first character +# it will be automatically added in the LaTeX code. +# Note: This tag is used in the generated output file (.tex). +# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat. +# The default value is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_MAKEINDEX_CMD = makeindex + +# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used by the +# printer. +# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x +# 14 inches) and executive (7.25 x 10.5 inches). +# The default value is: a4. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names +# that should be included in the LaTeX output. The package can be specified just +# by its name or with the correct syntax as to be used with the LaTeX +# \usepackage command. To get the times font for instance you can specify : +# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times} +# To use the option intlimits with the amsmath package you can specify: +# EXTRA_PACKAGES=[intlimits]{amsmath} +# If left blank no extra packages will be included. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for +# the generated LaTeX document. The header should contain everything until the +# first chapter. If it is left blank doxygen will generate a standard header. It +# is highly recommended to start with a default header using +# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty +# and then modify the file new_header.tex. See also section "Doxygen usage" for +# information on how to generate the default header that doxygen normally uses. +# +# Note: Only use a user-defined header if you know what you are doing! +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. The following +# commands have a special meaning inside the header (and footer): For a +# description of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for +# the generated LaTeX document. The footer should contain everything after the +# last chapter. If it is left blank doxygen will generate a standard footer. See +# LATEX_HEADER for more information on how to generate a default footer and what +# special commands can be used inside the footer. See also section "Doxygen +# usage" for information on how to generate the default footer that doxygen +# normally uses. Note: Only use a user-defined footer if you know what you are +# doing! +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_FOOTER = + +# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# LaTeX style sheets that are included after the standard style sheets created +# by doxygen. Using this option one can overrule certain style aspects. Doxygen +# will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_STYLESHEET = + +# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the LATEX_OUTPUT output +# directory. Note that the files will be copied as-is; there are no commands or +# markers available. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_FILES = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is +# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will +# contain links (just like the HTML output) instead of page references. This +# makes the output suitable for online browsing using a PDF viewer. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as +# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX +# files. Set this option to YES, to get a higher quality PDF documentation. +# +# See also section LATEX_CMD_NAME for selecting the engine. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode +# command to the generated LaTeX files. This will instruct LaTeX to keep running +# if errors occur, instead of asking the user for help. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BATCHMODE = NO + +# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the +# index chapters (such as File Index, Compound Index, etc.) in the output. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HIDE_INDICES = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. See +# https://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# The default value is: plain. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BIB_STYLE = plain + +# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_TIMESTAMP = NO + +# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) +# path from which the emoji images will be read. If a relative path is entered, +# it will be relative to the LATEX_OUTPUT directory. If left blank the +# LATEX_OUTPUT directory will be used. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EMOJI_DIRECTORY = + +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The +# RTF output is optimized for Word 97 and may not look too pretty with other RTF +# readers/editors. +# The default value is: NO. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: rtf. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will +# contain hyperlink fields. The RTF file will contain links (just like the HTML +# output) instead of page references. This makes the output suitable for online +# browsing using Word or some other Word compatible readers that support those +# fields. +# +# Note: WordPad (write) and others do not support links. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# configuration file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. +# +# See also section "Doxygen usage" for information on how to generate the +# default style sheet that doxygen normally uses. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an RTF document. Syntax is +# similar to doxygen's configuration file. A template extensions file can be +# generated using doxygen -e rtf extensionFile. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for +# classes and files. +# The default value is: NO. + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. A directory man3 will be created inside the directory specified by +# MAN_OUTPUT. +# The default directory is: man. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to the generated +# man pages. In case the manual section does not start with a number, the number +# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is +# optional. +# The default value is: .3. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_EXTENSION = .3 + +# The MAN_SUBDIR tag determines the name of the directory created within +# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by +# MAN_EXTENSION with the initial . removed. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_SUBDIR = + +# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it +# will generate one additional man file for each entity documented in the real +# man page(s). These additional files only source the real man page, but without +# them the man command would be unable to find the correct page. +# The default value is: NO. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that +# captures the structure of the code including all documentation. +# The default value is: NO. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: xml. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_OUTPUT = xml + +# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program +# listings (including syntax highlighting and cross-referencing information) to +# the XML output. Note that enabling this will significantly increase the size +# of the XML output. +# The default value is: YES. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_PROGRAMLISTING = YES + +# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include +# namespace members in file scope as well, matching the HTML output. +# The default value is: NO. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_NS_MEMB_FILE_SCOPE = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- + +# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files +# that can be used to generate PDF. +# The default value is: NO. + +GENERATE_DOCBOOK = NO + +# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in +# front of it. +# The default directory is: docbook. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_OUTPUT = docbook + +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an +# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# the structure of the code including all documentation. Note that this feature +# is still experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module +# file that captures the structure of the code including all documentation. +# +# Note that this feature is still experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary +# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI +# output from the Perl module output. +# The default value is: NO. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely +# formatted so it can be parsed by a human reader. This is useful if you want to +# understand what is going on. On the other hand, if this tag is set to NO, the +# size of the Perl module output will be much smaller and Perl will parse it +# just the same. +# The default value is: YES. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file are +# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful +# so different doxyrules.make files included by the same Makefile don't +# overwrite each other's variables. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all +# C-preprocessor directives found in the sources and include files. +# The default value is: YES. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names +# in the source code. If set to NO, only conditional compilation will be +# performed. Macro expansion can be done in a controlled way by setting +# EXPAND_ONLY_PREDEF to YES. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then +# the macro expansion is limited to the macros specified with the PREDEFINED and +# EXPAND_AS_DEFINED tags. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES, the include files in the +# INCLUDE_PATH will be searched if a #include is found. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by the +# preprocessor. +# This tag requires that the tag SEARCH_INCLUDES is set to YES. + +INCLUDE_PATH = ../include + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will be +# used. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that are +# defined before the preprocessor is started (similar to the -D option of e.g. +# gcc). The argument of the tag is a list of macros of the form: name or +# name=definition (no spaces). If the definition and the "=" are omitted, "=1" +# is assumed. To prevent a macro definition from being undefined via #undef or +# recursively expanded use the := operator instead of the = operator. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this +# tag can be used to specify a list of macro names that should be expanded. The +# macro definition that is found in the sources will be used. Use the PREDEFINED +# tag if you want to use a different macro definition that overrules the +# definition found in the source code. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will +# remove all references to function-like macros that are alone on a line, have +# an all uppercase name, and do not end with a semicolon. Such function macros +# are typically used for boiler-plate code, and will confuse the parser if not +# removed. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES tag can be used to specify one or more tag files. For each tag +# file the location of the external documentation should be added. The format of +# a tag file without this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where loc1 and loc2 can be relative or absolute paths or URLs. See the +# section "Linking to external documentation" for more information about the use +# of tag files. +# Note: Each tag file must have a unique name (where the name does NOT include +# the path). If a tag file is not located in the directory in which doxygen is +# run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create a +# tag file that is based on the input files it reads. See section "Linking to +# external documentation" for more information about the usage of tag files. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES, all external class will be listed in +# the class index. If set to NO, only the inherited external classes will be +# listed. +# The default value is: NO. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will be +# listed. +# The default value is: YES. + +EXTERNAL_GROUPS = YES + +# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in +# the related pages index. If set to NO, only the current project's pages will +# be listed. +# The default value is: YES. + +EXTERNAL_PAGES = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. + +DIA_PATH = + +# If set to YES the inheritance and collaboration graphs will hide inheritance +# and usage relations if the target is undocumented or is not a class. +# The default value is: YES. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz (see: +# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# Bell Labs. The other options in this section have no effect if this option is +# set to NO +# The default value is: NO. + +HAVE_DOT = YES + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed +# to run in parallel. When set to 0 doxygen will base this on the number of +# processors available in the system. You can set it explicitly to a value +# larger than 0 to get control over the balance between CPU load and processing +# speed. +# Minimum value: 0, maximum value: 32, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NUM_THREADS = 0 + +# When you want a differently looking font in the dot files that doxygen +# generates you can specify the font name using DOT_FONTNAME. You need to make +# sure dot is able to find the font, which can be done by putting it in a +# standard location or by setting the DOTFONTPATH environment variable or by +# setting DOT_FONTPATH to the directory containing the font. +# The default value is: Helvetica. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of +# dot graphs. +# Minimum value: 4, maximum value: 24, default value: 10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the default font as specified with +# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set +# the path where dot can find it using this tag. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTPATH = + +# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a +# graph for each documented class showing the direct and indirect inheritance +# relations. In case HAVE_DOT is set as well dot will be used to draw the graph, +# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set +# to TEXT the direct and indirect inheritance relations will be shown as texts / +# links. +# Possible values are: NO, YES, TEXT and GRAPH. +# The default value is: YES. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# graph for each documented class showing the direct and indirect implementation +# dependencies (inheritance, containment, and class references variables) of the +# class with other documented classes. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for +# groups, showing the direct groups dependencies. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +UML_LOOK = YES + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside the +# class node. If there are many fields or methods and many nodes the graph may +# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the +# number of items for each type to make the size more manageable. Set this to 0 +# for no limit. Note that the threshold may be exceeded by 50% before the limit +# is enforced. So when you set the threshold to 10, up to 15 fields may appear, +# but if the number exceeds 15, the total amount of fields shown is limited to +# 10. +# Minimum value: 0, maximum value: 100, default value: 10. +# This tag requires that the tag UML_LOOK is set to YES. + +UML_LIMIT_NUM_FIELDS = 10 + +# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will wrapped across multiple lines. Some heuristics are apply +# to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + +# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and +# collaboration graphs will show the relations between templates and their +# instances. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +TEMPLATE_RELATIONS = NO + +# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to +# YES then doxygen will generate a graph for each documented file showing the +# direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDE_GRAPH = YES + +# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are +# set to YES then doxygen will generate a graph for each documented file showing +# the direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. Disabling a call graph can be +# accomplished by means of the command \hidecallgraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALL_GRAPH = YES + +# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. Disabling a caller graph can be +# accomplished by means of the command \hidecallergraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALLER_GRAPH = YES + +# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# hierarchy of all classes instead of a textual one. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# dependencies a directory has on other directories in a graphical way. The +# dependency relations are determined by the #include relations between the +# files in the directories. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +DIRECTORY_GRAPH = YES + +# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels +# of child directories generated in directory dependency graphs by dot. +# Minimum value: 1, maximum value: 25, default value: 1. +# This tag requires that the tag DIRECTORY_GRAPH is set to YES. + +DIR_GRAPH_MAX_DEPTH = 1 + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. For an explanation of the image formats see the section +# output formats in the documentation of the dot tool (Graphviz (see: +# http://www.graphviz.org/)). +# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order +# to make the SVG files visible in IE 9+ (other browsers do not have this +# requirement). +# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, +# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and +# png:gdiplus:gdiplus. +# The default value is: png. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_IMAGE_FORMAT = svg + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# +# Note that this requires a modern browser other than Internet Explorer. Tested +# and working are Firefox, Chrome, Safari, and Opera. +# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make +# the SVG files visible. Older versions of IE do not have SVG support. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +INTERACTIVE_SVG = YES + +# The DOT_PATH tag can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the \dotfile +# command). +# This tag requires that the tag HAVE_DOT is set to YES. + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = + +# The DIAFILE_DIRS tag can be used to specify one or more directories that +# contain dia files that are included in the documentation (see the \diafile +# command). + +DIAFILE_DIRS = + +# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the +# path where java can find the plantuml.jar file or to the filename of jar file +# to be used. If left blank, it is assumed PlantUML is not used or called during +# a preprocessing step. Doxygen will generate a warning when it encounters a +# \startuml command in this case and will not generate output for the diagram. + +PLANTUML_JAR_PATH = + +# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for plantuml. + +PLANTUML_CFG_FILE = + +# When using plantuml, the specified paths are searched for files specified by +# the !include statement in a plantuml block. + +PLANTUML_INCLUDE_PATH = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes +# that will be shown in the graph. If the number of nodes in a graph becomes +# larger than this value, doxygen will truncate the graph, which is visualized +# by representing a node as a red box. Note that doxygen if the number of direct +# children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that +# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. +# Minimum value: 0, maximum value: 10000, default value: 50. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs +# generated by dot. A depth value of 3 means that only nodes reachable from the +# root by following a path via at most 3 edges will be shown. Nodes that lay +# further from the root node will be omitted. Note that setting this option to 1 +# or 2 may greatly reduce the computation time needed for large code bases. Also +# note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. +# Minimum value: 0, maximum value: 1000, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not seem +# to support this out of the box. +# +# Warning: Depending on the platform used, enabling this option may lead to +# badly anti-aliased labels on the edges of a graph (i.e. they become hard to +# read). +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_TRANSPARENT = YES + +# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) support +# this, this feature is disabled by default. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_MULTI_TARGETS = YES + +# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# explaining the meaning of the various boxes and arrows in the dot generated +# graphs. +# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal +# graphical representation for inheritance and collaboration diagrams is used. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate +# files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc temporary +# files. +# The default value is: YES. + +DOT_CLEANUP = YES diff --git a/doc/doxygen/Doxyfile.in b/doc/doxygen/Doxyfile.in new file mode 100644 index 0000000..f4fba51 --- /dev/null +++ b/doc/doxygen/Doxyfile.in @@ -0,0 +1,2660 @@ +# Doxyfile 1.9.3 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "PLATO cmp_tool" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = @VERSION@ + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "PLATO Compression/Decompression Tool" + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = NO + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = YES + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = YES + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = YES + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# The default value is: system dependent. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if <section_label> ... \endif and \cond <section_label> +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = "/bin/sh -c 'git log --pretty=\"format:%ci, author:%aN <%aE>, commit:%h\" -1 \"${1}\" || echo no git'" + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = @SRCDIR@/doc/DoxygenLayout.xml + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = @SRCDIR@ + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, +# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C +# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, +# *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.l \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = @BUILDDIR@ + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# ANamespace::AClass, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = images #TODO + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# <filter> <input-file> +# +# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = YES + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = NO + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = @SRCDIR@/doc/header.html + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = @SRCDIR@/doc/footer.html + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = @SRCDIR@/doc/customdoxygen.css + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = @SRCDIR@/doc/bootstrap.min.css \ + @SRCDIR@/doc/bootstrap.min.js \ + @SRCDIR@/doc/doxy-boot.js + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = YES + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = YES + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/ + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see https://docs.mathjax.org/en/v2.7-latest/tex.html +# #tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use <access key> + S +# (what the <access key> is depends on the OS and browser, but it is typically +# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down +# key> to jump into the search results window, the results can be navigated +# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel +# the search. The filter options can be selected when the cursor is inside the +# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys> +# to select a filter and <Enter> or <escape> to activate or cancel the filter +# option. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +SEARCHENGINE = NO + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a web server instead of a web client using JavaScript. There +# are two flavors of web server based searching depending on the EXTERNAL_SEARCH +# setting. When disabled, doxygen will generate a PHP script for searching and +# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing +# and searching needs to be provided by external tools. See the section +# "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SERVER_BASED_SEARCH = NO + +# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP +# script for searching. Instead the search results are written to an XML file +# which needs to be processed by an external indexer. Doxygen will invoke an +# external search engine pointed to by the SEARCHENGINE_URL option to obtain the +# search results. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: +# https://xapian.org/). +# +# See the section "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH = NO + +# The SEARCHENGINE_URL should point to a search engine hosted by a web server +# which will return the search results when EXTERNAL_SEARCH is enabled. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: +# https://xapian.org/). See the section "External Indexing and Searching" for +# details. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHENGINE_URL = + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed +# search data is written to a file for indexing by an external tool. With the +# SEARCHDATA_FILE tag the name of this file can be specified. +# The default file is: searchdata.xml. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHDATA_FILE = searchdata.xml + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the +# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is +# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple +# projects and redirect the results back to the right project. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH_ID = + +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# projects other than the one defined by this configuration file, but that are +# all added to the same external search index. Each project needs to have a +# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of +# to a relative location where the documentation can be found. The format is: +# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. +# The default value is: YES. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: latex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. +# +# Note that when not enabling USE_PDFLATEX the default is latex when enabling +# USE_PDFLATEX the default is pdflatex and when in the later case latex is +# chosen this is overwritten by pdflatex. For specific output languages the +# default can have been set differently, this depends on the implementation of +# the output language. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_CMD_NAME = + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate +# index for LaTeX. +# Note: This tag is used in the Makefile / make.bat. +# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file +# (.tex). +# The default file is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +MAKEINDEX_CMD_NAME = makeindex + +# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to +# generate index for LaTeX. In case there is no backslash (\) as first character +# it will be automatically added in the LaTeX code. +# Note: This tag is used in the generated output file (.tex). +# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat. +# The default value is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_MAKEINDEX_CMD = makeindex + +# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used by the +# printer. +# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x +# 14 inches) and executive (7.25 x 10.5 inches). +# The default value is: a4. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names +# that should be included in the LaTeX output. The package can be specified just +# by its name or with the correct syntax as to be used with the LaTeX +# \usepackage command. To get the times font for instance you can specify : +# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times} +# To use the option intlimits with the amsmath package you can specify: +# EXTRA_PACKAGES=[intlimits]{amsmath} +# If left blank no extra packages will be included. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for +# the generated LaTeX document. The header should contain everything until the +# first chapter. If it is left blank doxygen will generate a standard header. It +# is highly recommended to start with a default header using +# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty +# and then modify the file new_header.tex. See also section "Doxygen usage" for +# information on how to generate the default header that doxygen normally uses. +# +# Note: Only use a user-defined header if you know what you are doing! +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. The following +# commands have a special meaning inside the header (and footer): For a +# description of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for +# the generated LaTeX document. The footer should contain everything after the +# last chapter. If it is left blank doxygen will generate a standard footer. See +# LATEX_HEADER for more information on how to generate a default footer and what +# special commands can be used inside the footer. See also section "Doxygen +# usage" for information on how to generate the default footer that doxygen +# normally uses. Note: Only use a user-defined footer if you know what you are +# doing! +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_FOOTER = + +# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# LaTeX style sheets that are included after the standard style sheets created +# by doxygen. Using this option one can overrule certain style aspects. Doxygen +# will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_STYLESHEET = + +# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the LATEX_OUTPUT output +# directory. Note that the files will be copied as-is; there are no commands or +# markers available. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_FILES = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is +# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will +# contain links (just like the HTML output) instead of page references. This +# makes the output suitable for online browsing using a PDF viewer. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as +# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX +# files. Set this option to YES, to get a higher quality PDF documentation. +# +# See also section LATEX_CMD_NAME for selecting the engine. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode +# command to the generated LaTeX files. This will instruct LaTeX to keep running +# if errors occur, instead of asking the user for help. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BATCHMODE = NO + +# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the +# index chapters (such as File Index, Compound Index, etc.) in the output. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HIDE_INDICES = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. See +# https://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# The default value is: plain. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BIB_STYLE = plain + +# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_TIMESTAMP = NO + +# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) +# path from which the emoji images will be read. If a relative path is entered, +# it will be relative to the LATEX_OUTPUT directory. If left blank the +# LATEX_OUTPUT directory will be used. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EMOJI_DIRECTORY = + +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The +# RTF output is optimized for Word 97 and may not look too pretty with other RTF +# readers/editors. +# The default value is: NO. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: rtf. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will +# contain hyperlink fields. The RTF file will contain links (just like the HTML +# output) instead of page references. This makes the output suitable for online +# browsing using Word or some other Word compatible readers that support those +# fields. +# +# Note: WordPad (write) and others do not support links. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# configuration file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. +# +# See also section "Doxygen usage" for information on how to generate the +# default style sheet that doxygen normally uses. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an RTF document. Syntax is +# similar to doxygen's configuration file. A template extensions file can be +# generated using doxygen -e rtf extensionFile. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for +# classes and files. +# The default value is: NO. + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. A directory man3 will be created inside the directory specified by +# MAN_OUTPUT. +# The default directory is: man. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to the generated +# man pages. In case the manual section does not start with a number, the number +# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is +# optional. +# The default value is: .3. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_EXTENSION = .3 + +# The MAN_SUBDIR tag determines the name of the directory created within +# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by +# MAN_EXTENSION with the initial . removed. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_SUBDIR = + +# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it +# will generate one additional man file for each entity documented in the real +# man page(s). These additional files only source the real man page, but without +# them the man command would be unable to find the correct page. +# The default value is: NO. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that +# captures the structure of the code including all documentation. +# The default value is: NO. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: xml. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_OUTPUT = xml + +# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program +# listings (including syntax highlighting and cross-referencing information) to +# the XML output. Note that enabling this will significantly increase the size +# of the XML output. +# The default value is: YES. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_PROGRAMLISTING = YES + +# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include +# namespace members in file scope as well, matching the HTML output. +# The default value is: NO. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_NS_MEMB_FILE_SCOPE = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- + +# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files +# that can be used to generate PDF. +# The default value is: NO. + +GENERATE_DOCBOOK = NO + +# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in +# front of it. +# The default directory is: docbook. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_OUTPUT = docbook + +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an +# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# the structure of the code including all documentation. Note that this feature +# is still experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module +# file that captures the structure of the code including all documentation. +# +# Note that this feature is still experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary +# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI +# output from the Perl module output. +# The default value is: NO. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely +# formatted so it can be parsed by a human reader. This is useful if you want to +# understand what is going on. On the other hand, if this tag is set to NO, the +# size of the Perl module output will be much smaller and Perl will parse it +# just the same. +# The default value is: YES. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file are +# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful +# so different doxyrules.make files included by the same Makefile don't +# overwrite each other's variables. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all +# C-preprocessor directives found in the sources and include files. +# The default value is: YES. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names +# in the source code. If set to NO, only conditional compilation will be +# performed. Macro expansion can be done in a controlled way by setting +# EXPAND_ONLY_PREDEF to YES. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then +# the macro expansion is limited to the macros specified with the PREDEFINED and +# EXPAND_AS_DEFINED tags. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES, the include files in the +# INCLUDE_PATH will be searched if a #include is found. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by the +# preprocessor. +# This tag requires that the tag SEARCH_INCLUDES is set to YES. + +INCLUDE_PATH = @SRCDIR@/include @BUILDDIR@/include + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will be +# used. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that are +# defined before the preprocessor is started (similar to the -D option of e.g. +# gcc). The argument of the tag is a list of macros of the form: name or +# name=definition (no spaces). If the definition and the "=" are omitted, "=1" +# is assumed. To prevent a macro definition from being undefined via #undef or +# recursively expanded use the := operator instead of the = operator. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this +# tag can be used to specify a list of macro names that should be expanded. The +# macro definition that is found in the sources will be used. Use the PREDEFINED +# tag if you want to use a different macro definition that overrules the +# definition found in the source code. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will +# remove all references to function-like macros that are alone on a line, have +# an all uppercase name, and do not end with a semicolon. Such function macros +# are typically used for boiler-plate code, and will confuse the parser if not +# removed. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES tag can be used to specify one or more tag files. For each tag +# file the location of the external documentation should be added. The format of +# a tag file without this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where loc1 and loc2 can be relative or absolute paths or URLs. See the +# section "Linking to external documentation" for more information about the use +# of tag files. +# Note: Each tag file must have a unique name (where the name does NOT include +# the path). If a tag file is not located in the directory in which doxygen is +# run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create a +# tag file that is based on the input files it reads. See section "Linking to +# external documentation" for more information about the usage of tag files. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES, all external class will be listed in +# the class index. If set to NO, only the inherited external classes will be +# listed. +# The default value is: NO. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will be +# listed. +# The default value is: YES. + +EXTERNAL_GROUPS = YES + +# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in +# the related pages index. If set to NO, only the current project's pages will +# be listed. +# The default value is: YES. + +EXTERNAL_PAGES = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. + +DIA_PATH = + +# If set to YES the inheritance and collaboration graphs will hide inheritance +# and usage relations if the target is undocumented or is not a class. +# The default value is: YES. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz (see: +# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# Bell Labs. The other options in this section have no effect if this option is +# set to NO +# The default value is: NO. + +HAVE_DOT = @HAVE_DOT@ + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed +# to run in parallel. When set to 0 doxygen will base this on the number of +# processors available in the system. You can set it explicitly to a value +# larger than 0 to get control over the balance between CPU load and processing +# speed. +# Minimum value: 0, maximum value: 32, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NUM_THREADS = 0 + +# When you want a differently looking font in the dot files that doxygen +# generates you can specify the font name using DOT_FONTNAME. You need to make +# sure dot is able to find the font, which can be done by putting it in a +# standard location or by setting the DOTFONTPATH environment variable or by +# setting DOT_FONTPATH to the directory containing the font. +# The default value is: Helvetica. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of +# dot graphs. +# Minimum value: 4, maximum value: 24, default value: 10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the default font as specified with +# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set +# the path where dot can find it using this tag. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTPATH = + +# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a +# graph for each documented class showing the direct and indirect inheritance +# relations. In case HAVE_DOT is set as well dot will be used to draw the graph, +# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set +# to TEXT the direct and indirect inheritance relations will be shown as texts / +# links. +# Possible values are: NO, YES, TEXT and GRAPH. +# The default value is: YES. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# graph for each documented class showing the direct and indirect implementation +# dependencies (inheritance, containment, and class references variables) of the +# class with other documented classes. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for +# groups, showing the direct groups dependencies. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +UML_LOOK = YES + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside the +# class node. If there are many fields or methods and many nodes the graph may +# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the +# number of items for each type to make the size more manageable. Set this to 0 +# for no limit. Note that the threshold may be exceeded by 50% before the limit +# is enforced. So when you set the threshold to 10, up to 15 fields may appear, +# but if the number exceeds 15, the total amount of fields shown is limited to +# 10. +# Minimum value: 0, maximum value: 100, default value: 10. +# This tag requires that the tag UML_LOOK is set to YES. + +UML_LIMIT_NUM_FIELDS = 10 + +# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will wrapped across multiple lines. Some heuristics are apply +# to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + +# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and +# collaboration graphs will show the relations between templates and their +# instances. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +TEMPLATE_RELATIONS = NO + +# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to +# YES then doxygen will generate a graph for each documented file showing the +# direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDE_GRAPH = YES + +# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are +# set to YES then doxygen will generate a graph for each documented file showing +# the direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. Disabling a call graph can be +# accomplished by means of the command \hidecallgraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALL_GRAPH = YES + +# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. Disabling a caller graph can be +# accomplished by means of the command \hidecallergraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALLER_GRAPH = YES + +# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# hierarchy of all classes instead of a textual one. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# dependencies a directory has on other directories in a graphical way. The +# dependency relations are determined by the #include relations between the +# files in the directories. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +DIRECTORY_GRAPH = YES + +# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels +# of child directories generated in directory dependency graphs by dot. +# Minimum value: 1, maximum value: 25, default value: 1. +# This tag requires that the tag DIRECTORY_GRAPH is set to YES. + +DIR_GRAPH_MAX_DEPTH = 1 + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. For an explanation of the image formats see the section +# output formats in the documentation of the dot tool (Graphviz (see: +# http://www.graphviz.org/)). +# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order +# to make the SVG files visible in IE 9+ (other browsers do not have this +# requirement). +# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, +# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and +# png:gdiplus:gdiplus. +# The default value is: png. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_IMAGE_FORMAT = svg + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# +# Note that this requires a modern browser other than Internet Explorer. Tested +# and working are Firefox, Chrome, Safari, and Opera. +# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make +# the SVG files visible. Older versions of IE do not have SVG support. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +INTERACTIVE_SVG = YES + +# The DOT_PATH tag can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the \dotfile +# command). +# This tag requires that the tag HAVE_DOT is set to YES. + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = + +# The DIAFILE_DIRS tag can be used to specify one or more directories that +# contain dia files that are included in the documentation (see the \diafile +# command). + +DIAFILE_DIRS = + +# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the +# path where java can find the plantuml.jar file or to the filename of jar file +# to be used. If left blank, it is assumed PlantUML is not used or called during +# a preprocessing step. Doxygen will generate a warning when it encounters a +# \startuml command in this case and will not generate output for the diagram. + +PLANTUML_JAR_PATH = + +# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for plantuml. + +PLANTUML_CFG_FILE = + +# When using plantuml, the specified paths are searched for files specified by +# the !include statement in a plantuml block. + +PLANTUML_INCLUDE_PATH = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes +# that will be shown in the graph. If the number of nodes in a graph becomes +# larger than this value, doxygen will truncate the graph, which is visualized +# by representing a node as a red box. Note that doxygen if the number of direct +# children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that +# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. +# Minimum value: 0, maximum value: 10000, default value: 50. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs +# generated by dot. A depth value of 3 means that only nodes reachable from the +# root by following a path via at most 3 edges will be shown. Nodes that lay +# further from the root node will be omitted. Note that setting this option to 1 +# or 2 may greatly reduce the computation time needed for large code bases. Also +# note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. +# Minimum value: 0, maximum value: 1000, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not seem +# to support this out of the box. +# +# Warning: Depending on the platform used, enabling this option may lead to +# badly anti-aliased labels on the edges of a graph (i.e. they become hard to +# read). +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_TRANSPARENT = YES + +# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) support +# this, this feature is disabled by default. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_MULTI_TARGETS = YES + +# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# explaining the meaning of the various boxes and arrows in the dot generated +# graphs. +# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal +# graphical representation for inheritance and collaboration diagrams is used. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate +# files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc temporary +# files. +# The default value is: YES. + +DOT_CLEANUP = YES diff --git a/doc/doxygen/DoxygenLayout.xml b/doc/doxygen/DoxygenLayout.xml new file mode 100644 index 0000000..7479687 --- /dev/null +++ b/doc/doxygen/DoxygenLayout.xml @@ -0,0 +1,194 @@ +<doxygenlayout version="1.0"> + <!-- Generated by doxygen 1.8.11 --> + <!-- Navigation index tabs for HTML output --> + <navindex> + <tab type="mainpage" visible="yes" title=""/> + <tab type="pages" visible="yes" title="" intro=""/> + <tab type="modules" visible="yes" title="" intro=""/> + <tab type="namespaces" visible="yes" title=""> + <tab type="namespacelist" visible="yes" title="" intro=""/> + <tab type="namespacemembers" visible="yes" title="" intro=""/> + </tab> + <tab type="classes" visible="yes" title=""> + <tab type="classlist" visible="yes" title="" intro=""/> + <tab type="classindex" visible="$ALPHABETICAL_INDEX" title=""/> + <tab type="hierarchy" visible="yes" title="" intro=""/> + <tab type="classmembers" visible="yes" title="" intro=""/> + </tab> + <tab type="files" visible="yes" title=""> + <tab type="filelist" visible="yes" title="" intro=""/> + <tab type="globals" visible="yes" title="" intro=""/> + </tab> + <tab type="examples" visible="yes" title="" intro=""/> + </navindex> + + <!-- Layout definition for a class page --> + <class> + <briefdescription visible="yes"/> + <includes visible="$SHOW_INCLUDE_FILES"/> + <inheritancegraph visible="$CLASS_GRAPH"/> + <collaborationgraph visible="$COLLABORATION_GRAPH"/> + <detaileddescription title=""/> + <memberdecl> + <nestedclasses visible="yes" title=""/> + <publictypes title=""/> + <services title=""/> + <interfaces title=""/> + <publicslots title=""/> + <signals title=""/> + <publicmethods title=""/> + <publicstaticmethods title=""/> + <publicattributes title=""/> + <publicstaticattributes title=""/> + <protectedtypes title=""/> + <protectedslots title=""/> + <protectedmethods title=""/> + <protectedstaticmethods title=""/> + <protectedattributes title=""/> + <protectedstaticattributes title=""/> + <packagetypes title=""/> + <packagemethods title=""/> + <packagestaticmethods title=""/> + <packageattributes title=""/> + <packagestaticattributes title=""/> + <properties title=""/> + <events title=""/> + <privatetypes title=""/> + <privateslots title=""/> + <privatemethods title=""/> + <privatestaticmethods title=""/> + <privateattributes title=""/> + <privatestaticattributes title=""/> + <friends title=""/> + <related title="" subtitle=""/> + <membergroups visible="yes"/> + </memberdecl> + <memberdef> + <inlineclasses title=""/> + <typedefs title=""/> + <enums title=""/> + <services title=""/> + <interfaces title=""/> + <constructors title=""/> + <functions title=""/> + <related title=""/> + <variables title=""/> + <properties title=""/> + <events title=""/> + </memberdef> + <allmemberslink visible="yes"/> + <usedfiles visible="$SHOW_USED_FILES"/> + <authorsection visible="yes"/> + </class> + + <!-- Layout definition for a namespace page --> + <namespace> + <briefdescription visible="yes"/> + <detaileddescription title=""/> + <memberdecl> + <nestednamespaces visible="yes" title=""/> + <constantgroups visible="yes" title=""/> + <classes visible="yes" title=""/> + <typedefs title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + <membergroups visible="yes"/> + </memberdecl> + <memberdef> + <inlineclasses title=""/> + <typedefs title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + </memberdef> + <authorsection visible="yes"/> + </namespace> + + <!-- Layout definition for a file page --> + <file> + <briefdescription visible="yes"/> + <includes visible="$SHOW_INCLUDE_FILES"/> + <includegraph visible="$INCLUDE_GRAPH"/> + <includedbygraph visible="$INCLUDED_BY_GRAPH"/> + <sourcelink visible="yes"/> + <detaileddescription title=""/> + <memberdecl> + <classes visible="yes" title=""/> + <namespaces visible="yes" title=""/> + <constantgroups visible="yes" title=""/> + <defines title=""/> + <typedefs title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + <membergroups visible="yes"/> + </memberdecl> + <memberdef> + <inlineclasses title=""/> + <defines title=""/> + <typedefs title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + </memberdef> + <authorsection/> + </file> + + <!-- Layout definition for a group page --> + <group> + <briefdescription visible="yes"/> + <groupgraph visible="$GROUP_GRAPHS"/> + <detaileddescription title=""/> + <memberdecl> + <nestedgroups visible="yes" title=""/> + <dirs visible="yes" title=""/> + <files visible="yes" title=""/> + <namespaces visible="yes" title=""/> + <classes visible="yes" title=""/> + <defines title=""/> + <typedefs title=""/> + <enums title=""/> + <enumvalues title=""/> + <functions title=""/> + <variables title=""/> + <signals title=""/> + <publicslots title=""/> + <protectedslots title=""/> + <privateslots title=""/> + <events title=""/> + <properties title=""/> + <friends title=""/> + <membergroups visible="yes"/> + </memberdecl> + <memberdef> + <pagedocs/> + <inlineclasses title=""/> + <defines title=""/> + <typedefs title=""/> + <enums title=""/> + <enumvalues title=""/> + <functions title=""/> + <variables title=""/> + <signals title=""/> + <publicslots title=""/> + <protectedslots title=""/> + <privateslots title=""/> + <events title=""/> + <properties title=""/> + <friends title=""/> + </memberdef> + <authorsection visible="yes"/> + </group> + + <!-- Layout definition for a directory page --> + <directory> + <briefdescription visible="yes"/> + <directorygraph visible="yes"/> + <memberdecl> + <dirs visible="yes"/> + <files visible="yes"/> + </memberdecl> + <detaileddescription title=""/> + </directory> +</doxygenlayout> diff --git a/doc/doxygen/bootstrap.min.css b/doc/doxygen/bootstrap.min.css new file mode 100644 index 0000000..4cf729e --- /dev/null +++ b/doc/doxygen/bootstrap.min.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap v3.3.6 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/doc/doxygen/bootstrap.min.js b/doc/doxygen/bootstrap.min.js new file mode 100644 index 0000000..e79c065 --- /dev/null +++ b/doc/doxygen/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.3.6 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under the MIT license + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>2)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 3")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.6",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.6",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.6",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.6",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.6",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&j<i.length-1&&j++,~j||(j=0),i.eq(j).trigger("focus")}}}};var h=a.fn.dropdown;a.fn.dropdown=d,a.fn.dropdown.Constructor=g,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=h,this},a(document).on("click.bs.dropdown.data-api",c).on("click.bs.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.bs.dropdown.data-api",f,g.prototype.toggle).on("keydown.bs.dropdown.data-api",f,g.prototype.keydown).on("keydown.bs.dropdown.data-api",".dropdown-menu",g.prototype.keydown)}(jQuery),+function(a){"use strict";function b(b,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},c.DEFAULTS,e.data(),"object"==typeof b&&b);f||e.data("bs.modal",f=new c(this,g)),"string"==typeof b?f[b](d):g.show&&f.show(d)})}var c=function(b,c){this.options=c,this.$body=a(document.body),this.$element=a(b),this.$dialog=this.$element.find(".modal-dialog"),this.$backdrop=null,this.isShown=null,this.originalBodyPad=null,this.scrollbarWidth=0,this.ignoreBackdropClick=!1,this.options.remote&&this.$element.find(".modal-content").load(this.options.remote,a.proxy(function(){this.$element.trigger("loaded.bs.modal")},this))};c.VERSION="3.3.6",c.TRANSITION_DURATION=300,c.BACKDROP_TRANSITION_DURATION=150,c.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},c.prototype.toggle=function(a){return this.isShown?this.hide():this.show(a)},c.prototype.show=function(b){var d=this,e=a.Event("show.bs.modal",{relatedTarget:b});this.$element.trigger(e),this.isShown||e.isDefaultPrevented()||(this.isShown=!0,this.checkScrollbar(),this.setScrollbar(),this.$body.addClass("modal-open"),this.escape(),this.resize(),this.$element.on("click.dismiss.bs.modal",'[data-dismiss="modal"]',a.proxy(this.hide,this)),this.$dialog.on("mousedown.dismiss.bs.modal",function(){d.$element.one("mouseup.dismiss.bs.modal",function(b){a(b.target).is(d.$element)&&(d.ignoreBackdropClick=!0)})}),this.backdrop(function(){var e=a.support.transition&&d.$element.hasClass("fade");d.$element.parent().length||d.$element.appendTo(d.$body),d.$element.show().scrollTop(0),d.adjustDialog(),e&&d.$element[0].offsetWidth,d.$element.addClass("in"),d.enforceFocus();var f=a.Event("shown.bs.modal",{relatedTarget:b});e?d.$dialog.one("bsTransitionEnd",function(){d.$element.trigger("focus").trigger(f)}).emulateTransitionEnd(c.TRANSITION_DURATION):d.$element.trigger("focus").trigger(f)}))},c.prototype.hide=function(b){b&&b.preventDefault(),b=a.Event("hide.bs.modal"),this.$element.trigger(b),this.isShown&&!b.isDefaultPrevented()&&(this.isShown=!1,this.escape(),this.resize(),a(document).off("focusin.bs.modal"),this.$element.removeClass("in").off("click.dismiss.bs.modal").off("mouseup.dismiss.bs.modal"),this.$dialog.off("mousedown.dismiss.bs.modal"),a.support.transition&&this.$element.hasClass("fade")?this.$element.one("bsTransitionEnd",a.proxy(this.hideModal,this)).emulateTransitionEnd(c.TRANSITION_DURATION):this.hideModal())},c.prototype.enforceFocus=function(){a(document).off("focusin.bs.modal").on("focusin.bs.modal",a.proxy(function(a){this.$element[0]===a.target||this.$element.has(a.target).length||this.$element.trigger("focus")},this))},c.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keydown.dismiss.bs.modal",a.proxy(function(a){27==a.which&&this.hide()},this)):this.isShown||this.$element.off("keydown.dismiss.bs.modal")},c.prototype.resize=function(){this.isShown?a(window).on("resize.bs.modal",a.proxy(this.handleUpdate,this)):a(window).off("resize.bs.modal")},c.prototype.hideModal=function(){var a=this;this.$element.hide(),this.backdrop(function(){a.$body.removeClass("modal-open"),a.resetAdjustments(),a.resetScrollbar(),a.$element.trigger("hidden.bs.modal")})},c.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},c.prototype.backdrop=function(b){var d=this,e=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var f=a.support.transition&&e;if(this.$backdrop=a(document.createElement("div")).addClass("modal-backdrop "+e).appendTo(this.$body),this.$element.on("click.dismiss.bs.modal",a.proxy(function(a){return this.ignoreBackdropClick?void(this.ignoreBackdropClick=!1):void(a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus():this.hide()))},this)),f&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;f?this.$backdrop.one("bsTransitionEnd",b).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):b()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var g=function(){d.removeBackdrop(),b&&b()};a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",g).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):g()}else b&&b()},c.prototype.handleUpdate=function(){this.adjustDialog()},c.prototype.adjustDialog=function(){var a=this.$element[0].scrollHeight>document.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth<a,this.scrollbarWidth=this.measureScrollbar()},c.prototype.setScrollbar=function(){var a=parseInt(this.$body.css("padding-right")||0,10);this.originalBodyPad=document.body.style.paddingRight||"",this.bodyIsOverflowing&&this.$body.css("padding-right",a+this.scrollbarWidth)},c.prototype.resetScrollbar=function(){this.$body.css("padding-right",this.originalBodyPad)},c.prototype.measureScrollbar=function(){var a=document.createElement("div");a.className="modal-scrollbar-measure",this.$body.append(a);var b=a.offsetWidth-a.clientWidth;return this.$body[0].removeChild(a),b};var d=a.fn.modal;a.fn.modal=b,a.fn.modal.Constructor=c,a.fn.modal.noConflict=function(){return a.fn.modal=d,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(c){var d=a(this),e=d.attr("href"),f=a(d.attr("data-target")||e&&e.replace(/.*(?=#[^\s]+$)/,"")),g=f.data("bs.modal")?"toggle":a.extend({remote:!/#/.test(e)&&e},f.data(),d.data());d.is("a")&&c.preventDefault(),f.one("show.bs.modal",function(a){a.isDefaultPrevented()||f.one("hidden.bs.modal",function(){d.is(":visible")&&d.trigger("focus")})}),b.call(f,g,this)})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.tooltip",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.type=null,this.options=null,this.enabled=null,this.timeout=null,this.hoverState=null,this.$element=null,this.inState=null,this.init("tooltip",a,b)};c.VERSION="3.3.6",c.TRANSITION_DURATION=150,c.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-m<o.top?"bottom":"right"==h&&k.right+l>o.width?"left":"left"==h&&k.left-l<o.left?"right":h,f.removeClass(n).addClass(h)}var p=this.getCalculatedOffset(h,k,l,m);this.applyPlacement(p,h);var q=function(){var a=e.hoverState;e.$element.trigger("shown.bs."+e.type),e.hoverState=null,"out"==a&&e.leave(e)};a.support.transition&&this.$tip.hasClass("fade")?f.one("bsTransitionEnd",q).emulateTransitionEnd(c.TRANSITION_DURATION):q()}},c.prototype.applyPlacement=function(b,c){var d=this.tip(),e=d[0].offsetWidth,f=d[0].offsetHeight,g=parseInt(d.css("margin-top"),10),h=parseInt(d.css("margin-left"),10);isNaN(g)&&(g=0),isNaN(h)&&(h=0),b.top+=g,b.left+=h,a.offset.setOffset(d[0],a.extend({using:function(a){d.css({top:Math.round(a.top),left:Math.round(a.left)})}},b),0),d.addClass("in");var i=d[0].offsetWidth,j=d[0].offsetHeight;"top"==c&&j!=f&&(b.top=b.top+f-j);var k=this.getViewportAdjustedDelta(c,b,i,j);k.left?b.left+=k.left:b.top+=k.top;var l=/top|bottom/.test(c),m=l?2*k.left-e+i:2*k.top-f+j,n=l?"offsetWidth":"offsetHeight";d.offset(b),this.replaceArrow(m,d[0][n],l)},c.prototype.replaceArrow=function(a,b,c){this.arrow().css(c?"left":"top",50*(1-a/b)+"%").css(c?"top":"left","")},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},c.prototype.hide=function(b){function d(){"in"!=e.hoverState&&f.detach(),e.$element.removeAttr("aria-describedby").trigger("hidden.bs."+e.type),b&&b()}var e=this,f=a(this.$tip),g=a.Event("hide.bs."+this.type);return this.$element.trigger(g),g.isDefaultPrevented()?void 0:(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one("bsTransitionEnd",d).emulateTransitionEnd(c.TRANSITION_DURATION):d(),this.hoverState=null,this)},c.prototype.fixTitle=function(){var a=this.$element;(a.attr("title")||"string"!=typeof a.attr("data-original-title"))&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},c.prototype.hasContent=function(){return this.getTitle()},c.prototype.getPosition=function(b){b=b||this.$element;var c=b[0],d="BODY"==c.tagName,e=c.getBoundingClientRect();null==e.width&&(e=a.extend({},e,{width:e.right-e.left,height:e.bottom-e.top}));var f=d?{top:0,left:0}:b.offset(),g={scroll:d?document.documentElement.scrollTop||document.body.scrollTop:b.scrollTop()},h=d?{width:a(window).width(),height:a(window).height()}:null;return a.extend({},e,g,h,f)},c.prototype.getCalculatedOffset=function(a,b,c,d){return"bottom"==a?{top:b.top+b.height,left:b.left+b.width/2-c/2}:"top"==a?{top:b.top-d,left:b.left+b.width/2-c/2}:"left"==a?{top:b.top+b.height/2-d/2,left:b.left-c}:{top:b.top+b.height/2-d/2,left:b.left+b.width}},c.prototype.getViewportAdjustedDelta=function(a,b,c,d){var e={top:0,left:0};if(!this.$viewport)return e;var f=this.options.viewport&&this.options.viewport.padding||0,g=this.getPosition(this.$viewport);if(/right|left/.test(a)){var h=b.top-f-g.scroll,i=b.top+f-g.scroll+d;h<g.top?e.top=g.top-h:i>g.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;j<g.left?e.left=g.left-j:k>g.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.6",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.6",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b<e[0])return this.activeTarget=null,this.clear();for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(void 0===e[a+1]||b<e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,this.clear();var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active"); +d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")},b.prototype.clear=function(){a(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.3.6",c.TRANSITION_DURATION=150,c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a"),f=a.Event("hide.bs.tab",{relatedTarget:b[0]}),g=a.Event("show.bs.tab",{relatedTarget:e[0]});if(e.trigger(f),b.trigger(g),!g.isDefaultPrevented()&&!f.isDefaultPrevented()){var h=a(d);this.activate(b.closest("li"),c),this.activate(h,h.parent(),function(){e.trigger({type:"hidden.bs.tab",relatedTarget:b[0]}),b.trigger({type:"shown.bs.tab",relatedTarget:e[0]})})}}},c.prototype.activate=function(b,d,e){function f(){g.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.6",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/doc/doxygen/customdoxygen.css b/doc/doxygen/customdoxygen.css new file mode 100644 index 0000000..6a32ca7 --- /dev/null +++ b/doc/doxygen/customdoxygen.css @@ -0,0 +1,306 @@ +/* +h1, .h1, h2, .h2, h3, .h3{ + font-weight: 200 !important; +} +*/ + +h1,.h1 { + font-size: 24px; +} + +h2,.h2 { + font-size: 20px; +} + +h3,.h3 { + font-size: 16px; +} + +h4,.h4 { + font-size: 14px; +} + +h5,.h5 { + font-size: 12px; +} + +h6,.h6 { + font-size: 10px; +} + + + +#navrow1, #navrow2, #navrow3, #navrow4, #navrow5{ + border-bottom: 1px solid #706d6e; +} + +.adjust-right { +margin-left: 30px !important; +font-size: 1.15em !important; +} +.navbar{ + border: 0px solid #222 !important; +} + + +/* Sticky footer styles +-------------------------------------------------- */ +html, +body { + height: 100%; + /* The html and body elements cannot have any padding or margin. */ +} + +img { +max-width:100%; +max-height:100%; +} + +/* Wrapper for page content to push down footer */ +#wrap { + min-height: 100%; + height: auto; + /* Negative indent footer by its height */ + margin: 0 auto -60px; + /* Pad bottom by footer height */ + padding: 0 0 60px; +} + +/* Set the fixed height of the footer here */ +#footer { + font-size: 0.9em; + padding: 8px 0px; + background-color: #f5f5f5; +} + +.footer-row { + line-height: 44px; +} + +#footer > .container { + padding-left: 15px; + padding-right: 15px; +} + +.footer-follow-icon { + margin-left: 3px; + text-decoration: none !important; +} + +.footer-follow-icon img { + width: 20px; +} + +.footer-link { + padding-top: 5px; + display: inline-block; + color: #999999; + text-decoration: none; +} + +.footer-copyright { + text-align: center; +} + + +@media (min-width: 992px) { + .footer-row { + text-align: left; + } + + .footer-icons { + text-align: right; + } +} +@media (max-width: 991px) { + .footer-row { + text-align: center; + } + + .footer-icons { + text-align: center; + } +} + +/* DOXYGEN Code Styles +----------------------------------- */ + + +div.ingroups { + font-size: 16pt; + width: 50%; + text-align: left; + padding-top: 10px; +} + +a.qindex { + font-size: 8pt; +} + +a.qindexHL { + font-size: 9pt; + font-weight: bold; + background-color: #9CAFD4; + color: #ffffff; + border: 1px double #869DCA; +} + +.contents a.qindexHL:visited { + color: #ffffff; +} + +a.code, a.code:visited, a.line, a.line:visited { + color: #4665A2; +} + +a.codeRef, a.codeRef:visited, a.lineRef, a.lineRef:visited { + color: #4665A2; +} + +/* @end */ + +dl.el { + margin-left: -1cm; +} + +pre.fragment { + border: 1px solid #C4CFE5; + background-color: #FBFCFD; + padding: 4px 6px; + margin: 4px 8px 4px 2px; + overflow: auto; + word-wrap: break-word; + font-size: 8pt; + line-height: 125%; + font-family: monospace, fixed; +} + +div.navtab { + text-align: left; + padding-left: 5px; + margin-right: 5px; +} + +div.fragment { + padding: 4px 6px; + margin: 4px 8px 4px 2px; + border: 1px solid #C4CFE5; +} + +div.line { + font-family: monospace, fixed; + font-size: 13px; + min-height: 13px; + line-height: 1.0; + text-wrap: unrestricted; + white-space: -moz-pre-wrap; /* Moz */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + white-space: pre-wrap; /* CSS3 */ + word-wrap: break-word; /* IE 5.5+ */ + text-indent: -53px; + padding-left: 53px; + padding-bottom: 0px; + margin: 0px; + -webkit-transition-property: background-color, box-shadow; + -webkit-transition-duration: 0.5s; + -moz-transition-property: background-color, box-shadow; + -moz-transition-duration: 0.5s; + -ms-transition-property: background-color, box-shadow; + -ms-transition-duration: 0.5s; + -o-transition-property: background-color, box-shadow; + -o-transition-duration: 0.5s; + transition-property: background-color, box-shadow; + transition-duration: 0.5s; +} + +div.line.glow { + background-color: cyan; + box-shadow: 0 0 10px cyan; +} + + +span.lineno { + padding-right: 4px; + text-align: right; + border-right: 2px solid #0F0; + background-color: #E8E8E8; + white-space: pre; +} +span.lineno a { + background-color: #D8D8D8; +} + +span.lineno a:hover { + background-color: #C8C8C8; +} + +div.groupHeader { + margin-left: 16px; + margin-top: 12px; + font-weight: bold; +} + +div.groupText { + margin-left: 16px; + font-style: italic; +} + +.caption { + font-weight: bold; + padding-top: 10px; + padding-bottom: 20px; +} +/* @group Code Colorization */ + +span.keyword { + color: #008000 +} + +span.keywordtype { + color: #604020 +} + +span.keywordflow { + color: #e08000 +} + +span.comment { + color: #800000 +} + +span.preprocessor { + color: #806020 +} + +span.stringliteral { + color: #002080 +} + +span.charliteral { + color: #008080 +} + +span.vhdldigit { + color: #ff00ff +} + +span.vhdlchar { + color: #000000 +} + +span.vhdlkeyword { + color: #700070 +} + +span.vhdllogic { + color: #ff0000 +} + +blockquote { + background-color: #F7F8FB; + border-left: 2px solid #9CAFD4; + margin: 0 24px 0 4px; + padding: 0 12px 0 16px; +} + diff --git a/doc/doxygen/doxy-boot.js b/doc/doxygen/doxy-boot.js new file mode 100644 index 0000000..f045d16 --- /dev/null +++ b/doc/doxygen/doxy-boot.js @@ -0,0 +1,121 @@ +$( document ).ready(function() { + + $("div.headertitle").addClass("page-header"); + $("div.title").addClass("h1"); + + $('li > a[href="index.html"] > span').before("<i class='fa fa-cog'></i> "); + $('li > a[href="index.html"] > span').text("CHEOPS IBSW"); + $('li > a[href="modules.html"] > span').before("<i class='fa fa-square'></i> "); + $('li > a[href="namespaces.html"] > span').before("<i class='fa fa-bars'></i> "); + $('li > a[href="annotated.html"] > span').before("<i class='fa fa-list-ul'></i> "); + $('li > a[href="classes.html"] > span').before("<i class='fa fa-book'></i> "); + $('li > a[href="inherits.html"] > span').before("<i class='fa fa-sitemap'></i> "); + $('li > a[href="functions.html"] > span').before("<i class='fa fa-list'></i> "); + $('li > a[href="functions_func.html"] > span').before("<i class='fa fa-list'></i> "); + $('li > a[href="functions_vars.html"] > span').before("<i class='fa fa-list'></i> "); + $('li > a[href="functions_enum.html"] > span').before("<i class='fa fa-list'></i> "); + $('li > a[href="functions_eval.html"] > span').before("<i class='fa fa-list'></i> "); + $('img[src="ftv2ns.png"]').replaceWith('<span class="label label-danger">N</span> '); + $('img[src="ftv2cl.png"]').replaceWith('<span class="label label-danger">C</span> '); + + $("ul.tablist").addClass("nav nav-pills nav-justified"); + $("ul.tablist").css("margin-top", "0.5em"); + $("ul.tablist").css("margin-bottom", "0.5em"); + $("li.current").addClass("active"); + $("iframe").attr("scrolling", "yes"); + + $("#nav-path > ul").addClass("breadcrumb"); + + $("table.params").addClass("table"); + $("div.ingroups").wrapInner("<small></small>"); + $("div.levels").css("margin", "0.5em"); + $("div.levels > span").addClass("btn btn-default btn-xs"); + $("div.levels > span").css("margin-right", "0.25em"); + + $("table.directory").addClass("table table-striped"); + $("div.summary > a").addClass("btn btn-default btn-xs"); + $("table.fieldtable").addClass("table"); + $(".fragment").addClass("well"); + $(".memitem").addClass("panel panel-default"); + $(".memproto").addClass("panel-heading"); + $(".memdoc").addClass("panel-body"); + $("span.mlabel").addClass("label label-info"); + + $("table.memberdecls").addClass("table"); + $("[class^=memitem]").addClass("active"); + + $("div.ah").addClass("btn btn-default"); + $("span.mlabels").addClass("pull-right"); + $("table.mlabels").css("width", "100%") + $("td.mlabels-right").addClass("pull-right"); + + $("div.ttc").addClass("panel panel-primary"); + $("div.ttname").addClass("panel-heading"); + $("div.ttname a").css("color", 'white'); + $("div.ttdef,div.ttdoc,div.ttdeci").addClass("panel-body"); + + $('#MSearchBox').parent().remove(); + + $('div.fragment.well div.line:first').css('margin-top', '15px'); + $('div.fragment.well div.line:last').css('margin-bottom', '15px'); + + $('table.doxtable').removeClass('doxtable').addClass('table table-striped table-bordered').each(function(){ + $(this).prepend('<thead></thead>'); + $(this).find('tbody > tr:first').prependTo($(this).find('thead')); + + $(this).find('td > span.success').parent().addClass('success'); + $(this).find('td > span.warning').parent().addClass('warning'); + $(this).find('td > span.danger').parent().addClass('danger'); + }); + + + + if($('div.fragment.well div.ttc').length > 0) + { + $('div.fragment.well div.line:first').parent().removeClass('fragment well'); + } + + $('table.memberdecls').find('.memItemRight').each(function(){ + $(this).contents().appendTo($(this).siblings('.memItemLeft')); + $(this).siblings('.memItemLeft').attr('align', 'left'); + }); + + function getOriginalWidthOfImg(img_element) { + var t = new Image(); + t.src = (img_element.getAttribute ? img_element.getAttribute("src") : false) || img_element.src; + return t.width; + } + + $('div.dyncontent').find('img').each(function(){ + if(getOriginalWidthOfImg($(this)[0]) > $('#content>div.container').width()) + $(this).css('width', '100%'); + }); + + $(".memitem").removeClass('memitem'); + $(".memproto").removeClass('memproto'); + $(".memdoc").removeClass('memdoc'); + $("span.mlabel").removeClass('mlabel'); + $("table.memberdecls").removeClass('memberdecls'); + $("[class^=memitem]").removeClass('memitem'); + $("span.mlabels").removeClass('mlabels'); + $("table.mlabels").removeClass('mlabels'); + $("td.mlabels-right").removeClass('mlabels-right'); + $(".navpath").removeClass('navpath'); + $("li.navelem").removeClass('navelem'); + $("a.el").removeClass('el'); + $("div.ah").removeClass('ah'); + $("div.header").removeClass("header"); + + $('.mdescLeft').each(function(){ + if($(this).html()==" ") { + $(this).siblings('.mdescRight').attr('colspan', 2); + $(this).remove(); + } + }); + $('td.memItemLeft').each(function(){ + if($(this).siblings('.memItemRight').html()=="") { + $(this).attr('colspan', 2); + $(this).siblings('.memItemRight').remove(); + } + }); +}); diff --git a/doc/doxygen/footer.html b/doc/doxygen/footer.html new file mode 100644 index 0000000..f2fa204 --- /dev/null +++ b/doc/doxygen/footer.html @@ -0,0 +1,26 @@ +<!-- HTML footer for doxygen 1.8.8--> +<!-- start footer part --> +<!--BEGIN GENERATE_TREEVIEW--> +<div id="nav-path" class="navpath"><!-- id is needed for treeview function! --> + <ul> + $navpath + <li class="footer">$generatedby + <a href="http://www.doxygen.org/index.html"> + <img class="footer" src="$relpath^doxygen.png" alt="doxygen"/></a> $doxygenversion </li> + </ul> +</div> +<!--END GENERATE_TREEVIEW--> +</div> +</div> +</div> +</div> +</div> +<!--BEGIN !GENERATE_TREEVIEW--> +<hr class="footer"/><address class="footer"><small> +$generatedby  <a href="http://www.doxygen.org/index.html"> +<img class="footer" src="$relpath^doxygen.png" alt="doxygen"/> +</a> $doxygenversion +</small></address> +<!--END !GENERATE_TREEVIEW--> +</body> +</html> diff --git a/doc/doxygen/header.html b/doc/doxygen/header.html new file mode 100644 index 0000000..d4b85eb --- /dev/null +++ b/doc/doxygen/header.html @@ -0,0 +1,47 @@ +<!-- HTML header for doxygen 1.8.8--> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <!-- For Mobile Devices --> + <meta name="viewport" content="width=device-width, initial-scale=1"> + + <meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> + <meta name="generator" content="Doxygen $doxygenversion"/> + + <script type="text/javascript" src="jquery.js"></script> + + <!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME--> + <!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME--> + <!--<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>--> + <script type="text/javascript" src="$relpath^dynsections.js"></script> + $treeview + $search + $mathjax + <link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" /> + + <link rel="stylesheet" href="$relpath^bootstrap.min.css"> + + $extrastylesheet + + <script src="$relpath^bootstrap.min.js"></script> + <script type="text/javascript" src="$relpath^doxy-boot.js"></script> + </head> + <body> + + <nav class="navbar navbar-default" role="navigation"> + <div class="container"> + <div class="navbar-header"> + <a class="navbar-brand"> + <img alt="Logo" align="left" style="margin-right: 1em;" src=$projectlogo/> + $projectname $projectnumber</a> + </div> + </div> + </nav> + <div id="top"><!-- do not remove this div, it is closed by doxygen! --> + <div class="content" id="content"> + <div class="container"> + <div class="row"> + <div class="col-sm-12 panel panel-default" style="padding-bottom: 15px;"> + <div style="margin-bottom: 15px;"> +<!-- end header part --> diff --git a/doc/doxygen/meson.build b/doc/doxygen/meson.build new file mode 100644 index 0000000..b500bac --- /dev/null +++ b/doc/doxygen/meson.build @@ -0,0 +1,38 @@ +doc_layout_files = files([ + 'DoxygenLayout.xml', + 'bootstrap.min.css', + 'customdoxygen.css', + 'bootstrap.min.js', + 'doxy-boot.js', + 'header.html', + 'footer.html' +]) + +doxygen = find_program('doxygen', required : false) + +#Build a Doxyfile based on Doxyfile.in +cdata_doc = configuration_data() +cdata_doc.set('VERSION', meson.project_version()) +cdata_doc.set('SRCDIR', meson.project_source_root()) +cdata_doc.set('BUILDDIR', meson.project_build_root()) +if find_program('dot', required : false).found() + cdata_doc.set('HAVE_DOT', 'YES') +else + cdata_doc.set('HAVE_DOT', 'NO') +endif + +doxy_file = configure_file( + input : 'Doxyfile.in', + output : 'Doxyfile', + configuration : cdata_doc, + install : false, +) + +custom_target('doc', + input : doxy_file, + output : 'html', + depend_files : [doc_layout_files, main, cmplib_sources], + command : [doxygen, '@INPUT@'], + build_by_default : false, + console : true, +) diff --git a/include/meson.build b/include/meson.build new file mode 100644 index 0000000..d5946c8 --- /dev/null +++ b/include/meson.build @@ -0,0 +1,13 @@ +# generate the configuration file +cdata = configuration_data() +cdata.set_quoted('PROGRAM_NAME', 'cmp_tool') +cdata.set_quoted('VERSION_STR', meson.project_version()) +if get_option('argument_input_mode') + cdata.set('ARGUMENT_INPUT_MODE', '') +endif + +configure_file( + output : 'cmp_tool-config.h', + configuration : cdata) + +incdir = include_directories('.') diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..59f2c05 --- /dev/null +++ b/meson.build @@ -0,0 +1,27 @@ +project('cmp_tool', 'c', + version : '0.09', + license : 'GPL-2.0', + default_options : ['warning_level=3', 'c_std=gnu99'] +) + +if host_machine.system() == 'windows' and cc.get_id() == 'gcc' + # by default, MinGW on win32 behaves as if it ignores __attribute__((packed)), + # you need to add -mno-ms-bitfields to make it work as expected. + # See: https://wintermade.it/blog/posts/__attribute__packed-on-windows-is-ignored-with-mingw.html + add_project_arguments('-mno-ms-bitfields', language : 'c') +endif + +subdir('include') +subdir('lib') + +main = files('cmp_tool.c') + +cmp_tool = executable('cmp_tool', + sources : main, + include_directories : incdir, + link_with : cmp_lib, + install : 'true' +) + +subdir('test') +subdir('doc/doxygen') diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..fe8670e --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,2 @@ +option('argument_input_mode', type : 'boolean', value : false, + description : 'If set, the data file is set with the first argument and the model file with the second one') diff --git a/test/cmp_icu/meson.build b/test/cmp_icu/meson.build new file mode 100644 index 0000000..80c3e27 --- /dev/null +++ b/test/cmp_icu/meson.build @@ -0,0 +1,14 @@ +test_cmp_icu_files = files ([ + 'test_cmp_icu.c' +]) + +test_cmp_icu = executable('test_cmp_icu', + test_cmp_icu_files, + include_directories : incdir, + link_with : cmp_lib, + # c_args : ['-DMAIN'] + dependencies : cunit_dep, + build_by_default : false +) + +test('cmp_icu unit test', test_cmp_icu) diff --git a/test/cmp_icu/test_cmp_icu.c b/test/cmp_icu/test_cmp_icu.c new file mode 100644 index 0000000..8bf30d6 --- /dev/null +++ b/test/cmp_icu/test_cmp_icu.c @@ -0,0 +1,128 @@ +/* + * Simple example of a CUnit unit test. + * + * This program (crudely) demonstrates a very simple "black box" + * test of the standard library functions fprintf() and fread(). + * It uses suite initialization and cleanup functions to open + * and close a common temporary file used by the test functions. + * The test functions then write to and read from the temporary + * file in the course of testing the library functions. + * + * The 2 test functions are added to a single CUnit suite, and + * then run using the CUnit Basic interface. The output of the + * program (on CUnit version 2.0-2) is: + * + * CUnit : A Unit testing framework for C. + * http://cunit.sourceforge.net/ + * + * Suite: Suite_1 + * Test: test of fprintf() ... passed + * Test: test of fread() ... passed + * + * --Run Summary: Type Total Ran Passed Failed + * suites 1 1 n/a 0 + * tests 2 2 2 0 + * asserts 5 5 5 0 + */ + +#include <stdio.h> +#include <string.h> +#include "CUnit/Basic.h" + +/* Pointer to the file used by the tests. */ +static FILE* temp_file = NULL; + +/* The suite initialization function. + * Opens the temporary file used by the tests. + * Returns zero on success, non-zero otherwise. + */ +int init_suite1(void) +{ + if (NULL == (temp_file = fopen("temp.txt", "w+"))) { + return -1; + } + else { + return 0; + } +} + +/* The suite cleanup function. + * Closes the temporary file used by the tests. + * Returns zero on success, non-zero otherwise. + */ +int clean_suite1(void) +{ + if (0 != fclose(temp_file)) { + return -1; + } + else { + temp_file = NULL; + return 0; + } +} + +/* Simple test of fprintf(). + * Writes test data to the temporary file and checks + * whether the expected number of bytes were written. + */ +void testFPRINTF(void) +{ + int i1 = 10; + + if (NULL != temp_file) { + CU_ASSERT(0 == fprintf(temp_file, "")); + CU_ASSERT(2 == fprintf(temp_file, "Q\n")); + CU_ASSERT(7 == fprintf(temp_file, "i1 = %d", i1)); + } +} + +/* Simple test of fread(). + * Reads the data previously written by testFPRINTF() + * and checks whether the expected characters are present. + * Must be run after testFPRINTF(). + */ +void testFREAD(void) +{ + unsigned char buffer[20]; + + if (NULL != temp_file) { + rewind(temp_file); + CU_ASSERT(9 == fread(buffer, sizeof(unsigned char), 20, temp_file)); + CU_ASSERT(0 == strncmp(buffer, "Q\ni1 = 10", 9)); + } +} + +/* The main() function for setting up and running the tests. + * Returns a CUE_SUCCESS on successful running, another + * CUnit error code on failure. + */ +int main() +{ + CU_pSuite pSuite = NULL; + + /* initialize the CUnit test registry */ + if (CUE_SUCCESS != CU_initialize_registry()) + return CU_get_error(); + + /* add a suite to the registry */ + pSuite = CU_add_suite("Suite_1", init_suite1, clean_suite1); + if (NULL == pSuite) { + CU_cleanup_registry(); + return CU_get_error(); + } + + /* add the tests to the suite */ + /* NOTE - ORDER IS IMPORTANT - MUST TEST fread() AFTER fprintf() */ + if ((NULL == CU_add_test(pSuite, "test of fprintf()", testFPRINTF)) || + (NULL == CU_add_test(pSuite, "test of fread()", testFREAD))) + { + CU_cleanup_registry(); + return CU_get_error(); + } + + /* Run all tests using the CUnit Basic interface */ + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + CU_cleanup_registry(); + return CU_get_error(); +} diff --git a/test/cmp_tool/Makefile b/test/cmp_tool/Makefile deleted file mode 100644 index 543ea13..0000000 --- a/test/cmp_tool/Makefile +++ /dev/null @@ -1,25 +0,0 @@ -CC = gcc -SOURCEDIR = -INCLUDES = -I../../include -PATH += -CFLAGS := -O0 -W -Wall -Wextra -std=gnu99 -Werror -pedantic -g3\ - -fprofile-arcs -ftest-coverage -CPPFLAGS := -D__MAIN__ $(INCLUDES) -I$(SOURCEDIR) -LDFLAGS := -lcunit -SOURCES := $(wildcard *.c) ../../lib/cmp_tool_lib.c ../../lib/cmp_guess.c\ - ../../lib/cmp_support.c ../../lib/cmp_icu.c ../../lib/cmp_data_types.c -OBJECTS := $(patsubst %.c, $(BUILDDIR)/%.o, $(subst $(SOURCEDIR)/,, $(SOURCES))) -TARGET := test_cmp_tool - - -all: $(SOURCES) - $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $^ -o $(TARGET) - -coverage: all - ./$(TARGET) - lcov --rc lcov_branch_coverage=1 --capture --directory ./ --output-file coverage.info - genhtml --rc lcov_branch_coverage=1 --branch-coverage coverage.info --output-directory out - #firefox out/index.html - -clean: - rm -r $(TARGET) *.gcno *.gcda coverage.info out/ tmp_stderr.log diff --git a/test/cmp_tool/meson.build b/test/cmp_tool/meson.build new file mode 100644 index 0000000..b60e2cf --- /dev/null +++ b/test/cmp_tool/meson.build @@ -0,0 +1,4 @@ +int_test_file = files('cmp_tool_integration_test.py') + +pytest = find_program('pytest', required : false) +test('Integration Test', pytest, args : ['--color=yes', '-vvv', int_test_file]) diff --git a/test/meson.build b/test/meson.build new file mode 100644 index 0000000..14a5f0c --- /dev/null +++ b/test/meson.build @@ -0,0 +1,33 @@ +# add checkpatch syntax-check target +checkpatch = find_program('checkpatch.pl', 'checkpatch', required : false) +checkpatch_args = [ + '--no-tree', '-f', + '--show-types', + '--color=always', + '--ignore', 'SPDX_LICENSE_TAG,PREFER_DEFINED_ATTRIBUTE_MACRO,EMBEDDED_FILENAME,BLOCK_COMMENT_STYLE,EMBEDDED_FUNCTION_NAME', +] +run_target('syntax-check', + command : [checkpatch, checkpatch_args, main, cmplib_sources]) + +# add cppcheck inspector target +cppcheck = find_program('cppcheck', required : false) +cppcheck_args = [ + main, cmplib_sources, + '--clang', + '--cppcheck-build-dir='+meson.current_build_dir(), + '-I', 'include', + '--std=c89', + '--addon=misra.py', + '--bug-hunting', + '--enable=all', + '--inconclusive' +] +run_target('inspector', + command : [cppcheck, cppcheck_args] +) + +cunit_dep = dependency('cunit') + +subdir('cmp_tool') +subdir('cmp_icu') + -- GitLab From 07a0445dde048930cf8a2c80eb8c981b4e9d59a3 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Fri, 11 Feb 2022 12:24:15 +0100 Subject: [PATCH 02/46] adapt code to cmp_tool-config.h file --- cmp_tool.c | 23 +++++++++++----------- include/cmp_io.h | 1 - include/meson.build | 2 +- lib/cmp_io.c | 9 +++++---- test/cmp_tool/cmp_tool_integration_test.py | 11 +++++------ 5 files changed, 22 insertions(+), 24 deletions(-) mode change 100644 => 100755 test/cmp_tool/cmp_tool_integration_test.py diff --git a/cmp_tool.c b/cmp_tool.c index fd8401f..90aa1df 100755 --- a/cmp_tool.c +++ b/cmp_tool.c @@ -25,16 +25,15 @@ #include <getopt.h> #include <time.h> -#include "include/cmp_io.h" -#include "include/cmp_icu.h" -#include "include/decmp.h" -#include "include/cmp_guess.h" -#include "include/cmp_entity.h" -#include "include/rdcu_pkt_to_file.h" +#include "cmp_tool-config.h" +#include "cmp_io.h" +#include "cmp_icu.h" +#include "decmp.h" +#include "cmp_guess.h" +#include "cmp_entity.h" +#include "rdcu_pkt_to_file.h" -#define VERSION "0.08" - #define BUFFER_LENGTH_DEF_FAKTOR 2 @@ -190,7 +189,7 @@ int main(int argc, char **argv) verbose_en = 1; break; case 'V': /* --version */ - printf("%s version %s\n", PROGRAM_NAME, VERSION); + printf("%s version %s\n", PROGRAM_NAME, CMP_TOOL_VERSION); exit(EXIT_SUCCESS); break; case DIFF_CFG_OPTION: @@ -273,9 +272,9 @@ int main(int argc, char **argv) printf("#########################################################\n"); printf("### PLATO Compression/Decompression Tool Version %s ###\n", - VERSION); + CMP_TOOL_VERSION); printf("#########################################################\n"); - if (!strcmp(VERSION, "0.07") || !strcmp(VERSION, "0.08")) + if (!strcmp(CMP_TOOL_VERSION, "0.07") || !strcmp(CMP_TOOL_VERSION, "0.08")) printf("Info: Note that the behaviour of the cmp_tool has changed. From now on, the compressed data will be preceded by a header by default. The old behaviour can be achieved with the --no_header option.\n\n"); if (!data_file_name) { @@ -629,7 +628,7 @@ static int add_cmp_ent_hdr(struct cmp_cfg *cfg, struct cmp_info *info, cmp_bit_to_4byte(info->cmp_size)); ent = (struct cmp_entity *)cfg->icu_output_buf; - s = cmp_ent_build(ent, data_type, cmp_tool_gen_version_id(VERSION), + s = cmp_ent_build(ent, data_type, cmp_tool_gen_version_id(CMP_TOOL_VERSION), start_time, cmp_ent_create_timestamp(NULL), model_id, model_counter, info, cfg); if (!s) { diff --git a/include/cmp_io.h b/include/cmp_io.h index 77dce35..179d59f 100644 --- a/include/cmp_io.h +++ b/include/cmp_io.h @@ -20,7 +20,6 @@ #include "cmp_support.h" #include "cmp_entity.h" -#define PROGRAM_NAME "cmp_tool" #define MAX_CONFIG_LINE 256 #define DEFAULT_OUTPUT_PREFIX "OUTPUT" diff --git a/include/meson.build b/include/meson.build index d5946c8..491eef5 100644 --- a/include/meson.build +++ b/include/meson.build @@ -1,7 +1,7 @@ # generate the configuration file cdata = configuration_data() cdata.set_quoted('PROGRAM_NAME', 'cmp_tool') -cdata.set_quoted('VERSION_STR', meson.project_version()) +cdata.set_quoted('CMP_TOOL_VERSION', meson.project_version()) if get_option('argument_input_mode') cdata.set('ARGUMENT_INPUT_MODE', '') endif diff --git a/lib/cmp_io.c b/lib/cmp_io.c index 9f7fd72..9dc9ba1 100644 --- a/lib/cmp_io.c +++ b/lib/cmp_io.c @@ -24,10 +24,11 @@ #include <ctype.h> #include <sys/stat.h> -#include "../include/cmp_io.h" -#include "../include/cmp_support.h" -#include "../include/rdcu_cmd.h" -#include "../include/byteorder.h" +#include "cmp_tool-config.h" +#include "cmp_io.h" +#include "cmp_support.h" +#include "rdcu_cmd.h" +#include "byteorder.h" /** diff --git a/test/cmp_tool/cmp_tool_integration_test.py b/test/cmp_tool/cmp_tool_integration_test.py old mode 100644 new mode 100755 index f5a022b..72d32c4 --- a/test/cmp_tool/cmp_tool_integration_test.py +++ b/test/cmp_tool/cmp_tool_integration_test.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 +import pytest import subprocess import shlex import sys @@ -213,10 +215,7 @@ CMP_START_STR = \ """######################################################### ### PLATO Compression/Decompression Tool Version %s ### ######################################################### -Info: Note that the behaviour of the cmp_tool has changed. From now on, the compressed data will be preceded by a header by default. The old behaviour can be achieved with the --no_header option. - """ % (VERSION) -print(CMP_START_STR) CMP_START_STR_CMP = CMP_START_STR + "## Starting the compression ##\n" CMP_START_STR_DECMP = CMP_START_STR + "## Starting the decompression ##\n" @@ -277,8 +276,8 @@ def test_print_diff_cfg(): args = ['--diff_cfg', '--diff_cfg -a', '--diff_cfg --rdcu_par'] for i, arg in enumerate(args): returncode, stdout, stderr = call_cmp_tool(arg) - assert(returncode == EXIT_SUCCESS) assert(stderr == "") + assert(returncode == EXIT_SUCCESS) cfg = parse_key_value(stdout) assert(cfg['cmp_mode'] == '2') @@ -368,8 +367,8 @@ def test_compression_diff(): # generate test configuration with open(cfg_file_name, 'w', encoding='utf-8') as f: returncode, stdout, stderr = call_cmp_tool("--diff_cfg") - assert(returncode == EXIT_SUCCESS) assert(stderr == "") + assert(returncode == EXIT_SUCCESS) f.write(stdout) add_args = [" --no_header", ""] @@ -379,8 +378,8 @@ def test_compression_diff(): " -c "+cfg_file_name+" -d "+data_file_name + " -o "+output_prefix+add_arg) # check compression results - assert(returncode == EXIT_SUCCESS) assert(stderr == "") + assert(returncode == EXIT_SUCCESS) assert(stdout == CMP_START_STR_CMP + "Importing configuration file %s ... DONE\n" % (cfg_file_name) + "Importing data file %s ... \n" % (data_file_name) + -- GitLab From e76a8a58149c0b3a4aa8488347da2fe8b648d3c2 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Fri, 11 Feb 2022 12:29:57 +0100 Subject: [PATCH 03/46] add missing build file --- lib/meson.build | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 lib/meson.build diff --git a/lib/meson.build b/lib/meson.build new file mode 100644 index 0000000..02487b0 --- /dev/null +++ b/lib/meson.build @@ -0,0 +1,21 @@ +cmplib_sources = files([ + 'cmp_data_types.c', + 'cmp_icu.c', + 'cmp_support.c', + 'rdcu_ctrl.c', + 'rmap.c', + 'cmp_entity.c', + 'cmp_io.c', + 'decmp.c', + 'rdcu_pkt_to_file.c', + 'cmp_guess.c', + 'cmp_rdcu.c', + 'rdcu_cmd.c', + 'rdcu_rmap.c' +]) + +cmp_lib = static_library('cmp_lib', + sources : cmplib_sources, + include_directories : incdir, + c_args : ['-DDEBUGLEVEL=1'] +) -- GitLab From 672b918108bbcb9f4f0083f614af5ff72150d15e Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Fri, 11 Feb 2022 12:45:52 +0100 Subject: [PATCH 04/46] make test dependencies optional --- test/meson.build | 56 +++++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/test/meson.build b/test/meson.build index 14a5f0c..617df9c 100644 --- a/test/meson.build +++ b/test/meson.build @@ -1,33 +1,39 @@ # add checkpatch syntax-check target checkpatch = find_program('checkpatch.pl', 'checkpatch', required : false) -checkpatch_args = [ - '--no-tree', '-f', - '--show-types', - '--color=always', - '--ignore', 'SPDX_LICENSE_TAG,PREFER_DEFINED_ATTRIBUTE_MACRO,EMBEDDED_FILENAME,BLOCK_COMMENT_STYLE,EMBEDDED_FUNCTION_NAME', -] -run_target('syntax-check', - command : [checkpatch, checkpatch_args, main, cmplib_sources]) +if checkpatch.found() + checkpatch_args = [ + '--no-tree', '-f', + '--show-types', + '--color=always', + '--ignore', 'SPDX_LICENSE_TAG,PREFER_DEFINED_ATTRIBUTE_MACRO,EMBEDDED_FILENAME,BLOCK_COMMENT_STYLE,EMBEDDED_FUNCTION_NAME', + ] + run_target('syntax-check', + command : [checkpatch, checkpatch_args, main, cmplib_sources]) +endif # add cppcheck inspector target cppcheck = find_program('cppcheck', required : false) -cppcheck_args = [ - main, cmplib_sources, - '--clang', - '--cppcheck-build-dir='+meson.current_build_dir(), - '-I', 'include', - '--std=c89', - '--addon=misra.py', - '--bug-hunting', - '--enable=all', - '--inconclusive' -] -run_target('inspector', - command : [cppcheck, cppcheck_args] -) - -cunit_dep = dependency('cunit') +if cppcheck.found() + cppcheck_args = [ + main, cmplib_sources, + '--clang', + '--cppcheck-build-dir='+meson.current_build_dir(), + '-I', 'include', + '--std=c89', + '--addon=misra.py', + '--bug-hunting', + '--enable=all', + '--inconclusive' + ] + run_target('inspector', + command : [cppcheck, cppcheck_args] + ) +endif subdir('cmp_tool') -subdir('cmp_icu') + +cunit_dep = dependency('cunit') +if cunit_dep.found() + subdir('cmp_icu') +endif -- GitLab From b62f5556350c8d40e9bad7a659b3d41cfffe4ce2 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Fri, 11 Feb 2022 09:55:40 -0500 Subject: [PATCH 05/46] make doxygen to required --- doc/doxygen/meson.build | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/doc/doxygen/meson.build b/doc/doxygen/meson.build index b500bac..e3062e3 100644 --- a/doc/doxygen/meson.build +++ b/doc/doxygen/meson.build @@ -8,31 +8,34 @@ doc_layout_files = files([ 'footer.html' ]) -doxygen = find_program('doxygen', required : false) #Build a Doxyfile based on Doxyfile.in cdata_doc = configuration_data() cdata_doc.set('VERSION', meson.project_version()) -cdata_doc.set('SRCDIR', meson.project_source_root()) -cdata_doc.set('BUILDDIR', meson.project_build_root()) +cdata_doc.set('SRCDIR', meson.source_root()) +cdata_doc.set('BUILDDIR', meson.build_root()) if find_program('dot', required : false).found() cdata_doc.set('HAVE_DOT', 'YES') else cdata_doc.set('HAVE_DOT', 'NO') endif -doxy_file = configure_file( - input : 'Doxyfile.in', - output : 'Doxyfile', - configuration : cdata_doc, - install : false, -) +doxygen = find_program('doxygen', required : false) + +if doxygen.found() + doxy_file = configure_file( + input : 'Doxyfile.in', + output : 'Doxyfile', + configuration : cdata_doc, + install : false, + ) -custom_target('doc', - input : doxy_file, - output : 'html', - depend_files : [doc_layout_files, main, cmplib_sources], - command : [doxygen, '@INPUT@'], - build_by_default : false, - console : true, -) + custom_target('doc', + input : doxy_file, + output : 'html', + depend_files : [doc_layout_files, main, cmplib_sources], + command : [doxygen, '@INPUT@'], + build_by_default : false, + console : true, + ) +endif -- GitLab From edee18eab3fc153a5d658a1ccd987eb0de343bee Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Mon, 14 Feb 2022 10:53:15 +0000 Subject: [PATCH 06/46] adopt build sytem to windows --- lib/meson.build | 3 ++- meson.build | 3 ++- test/cmp_tool/cmp_tool_integration_test.py | 11 +++++++++-- test/meson.build | 2 +- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/meson.build b/lib/meson.build index 02487b0..21f72ad 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -17,5 +17,6 @@ cmplib_sources = files([ cmp_lib = static_library('cmp_lib', sources : cmplib_sources, include_directories : incdir, - c_args : ['-DDEBUGLEVEL=1'] + c_args : ['-DDEBUGLEVEL=1'], + install : 'true' # linking under windows mingw only works if this is set ) diff --git a/meson.build b/meson.build index 59f2c05..295699f 100644 --- a/meson.build +++ b/meson.build @@ -4,11 +4,12 @@ project('cmp_tool', 'c', default_options : ['warning_level=3', 'c_std=gnu99'] ) -if host_machine.system() == 'windows' and cc.get_id() == 'gcc' +if host_machine.system() == 'windows' and meson.get_compiler('c').get_id() == 'gcc' # by default, MinGW on win32 behaves as if it ignores __attribute__((packed)), # you need to add -mno-ms-bitfields to make it work as expected. # See: https://wintermade.it/blog/posts/__attribute__packed-on-windows-is-ignored-with-mingw.html add_project_arguments('-mno-ms-bitfields', language : 'c') + add_global_link_arguments('-static', language: 'c') endif subdir('include') diff --git a/test/cmp_tool/cmp_tool_integration_test.py b/test/cmp_tool/cmp_tool_integration_test.py index 72d32c4..9804d41 100755 --- a/test/cmp_tool/cmp_tool_integration_test.py +++ b/test/cmp_tool/cmp_tool_integration_test.py @@ -1136,6 +1136,11 @@ def test_model_fiel_erros(): "longlonglonglonglonglonglonglonglonglonglonglonglong" "longlonglonglonglonglonglonglonglonglonglonglonglong" "longlonglonglonglonglonglonglonglonglong") + if sys.platform == 'win32' or sys.platform == 'cygwin': + output_prefix = ("longlonglonglonglonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglonglonglonglong" + "longlonglonglonglonglonglonglonglonglonglong") returncode, stdout, stderr = call_cmp_tool( " -c "+cfg_file_name+" -d "+data_file_name + " -m "+model_file_name+" -o "+output_prefix) assert(returncode == EXIT_FAILURE) @@ -1147,8 +1152,10 @@ def test_model_fiel_erros(): "Compress data ... DONE\n" + "Write compressed data to file %s.cmp ... DONE\n" %(output_prefix) + "Write updated model to file %s_upmodel.dat ... FAILED\n" %(output_prefix)) - assert(stderr == "cmp_tool: %s_upmodel.dat: File name too long\n" % (output_prefix)) - # + if sys.platform == 'win32' or sys.platform == 'cygwin': + assert(stderr == "cmp_tool: %s_upmodel.dat: No such file or directory\n" % (output_prefix)) + else: + assert(stderr == "cmp_tool: %s_upmodel.dat: File name too long\n" % (output_prefix)) finally: del_file(data_file_name) diff --git a/test/meson.build b/test/meson.build index 617df9c..ea06655 100644 --- a/test/meson.build +++ b/test/meson.build @@ -32,7 +32,7 @@ endif subdir('cmp_tool') -cunit_dep = dependency('cunit') +cunit_dep = dependency('cunit', required : false) if cunit_dep.found() subdir('cmp_icu') endif -- GitLab From 6be4781a9432b662c470ec3f7c96871191bd1325 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Mon, 14 Feb 2022 17:01:29 +0100 Subject: [PATCH 07/46] add more verbose build messages --- meson.build | 1 + test/cmp_tool/meson.build | 6 +++++- test/meson.build | 4 +++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index 295699f..d1fac6d 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,6 @@ project('cmp_tool', 'c', version : '0.09', + meson_version : '>= 0.50', license : 'GPL-2.0', default_options : ['warning_level=3', 'c_std=gnu99'] ) diff --git a/test/cmp_tool/meson.build b/test/cmp_tool/meson.build index b60e2cf..25b47e2 100644 --- a/test/cmp_tool/meson.build +++ b/test/cmp_tool/meson.build @@ -1,4 +1,8 @@ int_test_file = files('cmp_tool_integration_test.py') pytest = find_program('pytest', required : false) -test('Integration Test', pytest, args : ['--color=yes', '-vvv', int_test_file]) +if pytest.found() + test('Integration Test', pytest, args : ['--color=yes', '-vvv', int_test_file]) +else + message('Pytest framework not found! Skipping integration tests.') +endif diff --git a/test/meson.build b/test/meson.build index ea06655..c3a6afb 100644 --- a/test/meson.build +++ b/test/meson.build @@ -20,7 +20,7 @@ if cppcheck.found() '--cppcheck-build-dir='+meson.current_build_dir(), '-I', 'include', '--std=c89', - '--addon=misra.py', + # '--addon=misra.py', '--bug-hunting', '--enable=all', '--inconclusive' @@ -35,5 +35,7 @@ subdir('cmp_tool') cunit_dep = dependency('cunit', required : false) if cunit_dep.found() subdir('cmp_icu') +else + message('C Unit Testing Framework not found! Skipping unit tests.') endif -- GitLab From 56eb00208b7b9358b0f9258e4e51914846b51acf Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Mon, 14 Feb 2022 17:38:39 +0100 Subject: [PATCH 08/46] integration test depends on cmp_tool executable --- test/cmp_tool/meson.build | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/cmp_tool/meson.build b/test/cmp_tool/meson.build index 25b47e2..7b5b382 100644 --- a/test/cmp_tool/meson.build +++ b/test/cmp_tool/meson.build @@ -2,7 +2,11 @@ int_test_file = files('cmp_tool_integration_test.py') pytest = find_program('pytest', required : false) if pytest.found() - test('Integration Test', pytest, args : ['--color=yes', '-vvv', int_test_file]) + test('Integration Test', + pytest, + args : ['--color=yes', '-vvv', int_test_file] + depends: cmp_lib, + workdir: messon.build_root()) else message('Pytest framework not found! Skipping integration tests.') endif -- GitLab From 575745ae0efa77e7ccd36681950256a753f30692 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Mon, 14 Feb 2022 12:08:39 -0500 Subject: [PATCH 09/46] fix erros --- meson.build | 2 +- test/cmp_tool/meson.build | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/meson.build b/meson.build index d1fac6d..300fa64 100644 --- a/meson.build +++ b/meson.build @@ -18,7 +18,7 @@ subdir('lib') main = files('cmp_tool.c') -cmp_tool = executable('cmp_tool', +cmp_tool_exe = executable('cmp_tool', sources : main, include_directories : incdir, link_with : cmp_lib, diff --git a/test/cmp_tool/meson.build b/test/cmp_tool/meson.build index 7b5b382..be04bb7 100644 --- a/test/cmp_tool/meson.build +++ b/test/cmp_tool/meson.build @@ -4,9 +4,9 @@ pytest = find_program('pytest', required : false) if pytest.found() test('Integration Test', pytest, - args : ['--color=yes', '-vvv', int_test_file] - depends: cmp_lib, - workdir: messon.build_root()) + args : ['--color=yes', '-vvv', int_test_file], + depends : cmp_tool_exe, + workdir : meson.project_build_root()) else message('Pytest framework not found! Skipping integration tests.') endif -- GitLab From 543bfe5ebf539a765446aacd2dac3e18a0151033 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Mon, 14 Feb 2022 18:24:11 +0100 Subject: [PATCH 10/46] remove deprecated features --- doc/doxygen/meson.build | 4 ++-- meson.build | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/doxygen/meson.build b/doc/doxygen/meson.build index e3062e3..33a3495 100644 --- a/doc/doxygen/meson.build +++ b/doc/doxygen/meson.build @@ -12,8 +12,8 @@ doc_layout_files = files([ #Build a Doxyfile based on Doxyfile.in cdata_doc = configuration_data() cdata_doc.set('VERSION', meson.project_version()) -cdata_doc.set('SRCDIR', meson.source_root()) -cdata_doc.set('BUILDDIR', meson.build_root()) +cdata_doc.set('SRCDIR', meson.project_source_root()) +cdata_doc.set('BUILDDIR', meson.project_build_root()) if find_program('dot', required : false).found() cdata_doc.set('HAVE_DOT', 'YES') else diff --git a/meson.build b/meson.build index 300fa64..d300c11 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('cmp_tool', 'c', version : '0.09', - meson_version : '>= 0.50', + meson_version : '>= 0.56', license : 'GPL-2.0', default_options : ['warning_level=3', 'c_std=gnu99'] ) -- GitLab From 3b656c6a6df27fcc55f748068cf575645d68931f Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Thu, 17 Feb 2022 16:18:00 +0100 Subject: [PATCH 11/46] Add build documentation --- INSTALL.md | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 4 +- 2 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 INSTALL.md diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..3653d0a --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,136 @@ +## Installation Instructions + +### Install git and python 3.6+ + +If you're on Linux, you probably already have these. On macOS and Windows, you can use the +[official Python installer](https://www.python.org/downloads/). + +### Install meson and ninja + +Meson 0.56 or newer is required. + +You can get meson through the Python package manager or using: + +```pip3 install meson``` + +Check if meson is included in your PATH. + +You should get `ninja` using your [package manager](https://github.com/ninja-build/ninja/wiki/Pre-built-Ninja-packages) or download the [official +release](https://github.com/ninja-build/ninja/releases) and put the `ninja` +binary in your PATH. + +### Get the Source Code + +We use the version control system git to get a copy of the source code. + +``` +git clone https://gitlab.phaidra.org/loidoltd15/cmp_tool.git +cd cmp_tool +``` + +### Build the cmp_tool for Debugging + +You can build the cmp_tool running: + +``` +meson builddir +``` + +This will automatically create the `builddir` directory and build **everything** **inside** it. + +``` +cd builddir +meson compile +``` + +Now you should find the cmp_tool executable in the folder. + +### Release Build + +If you want to build an optimized release build run: + +``` +meson build_relase_dir --buildtype=release +cd build_relase_dir +meson compile +``` + +You find the build executable in the `build_relase_dir` directory + +### Cross-compiling to native Windows + +To build the cmp_tool you can use the [Mingw-w64](https://www.mingw-w64.org). +Unfortunately, the cmp_tool does not support the Microsoft MSVC compiler. But with the Mingw-w64 GCC compiler, we can compile the cmp_tool for Windows. For this, you need the [Mingw-w64 tool chain](https://www.mingw-w64.org/downloads/). This also works on Linux and macOS. To compile for Windows, do this: + +``` +meson setup buiddir_win --cross-file=mingw-w64-64.txt +cd buiddir_win +meson compile +``` + +## Tests +### External dependencies + +To run the unit tests you need the [c unit testing framework](https://sourceforge.net/projects/cunit/). +To run the integration tests you need the [pytest](https://docs.pytest.org/en/7.0.x/index.html) framework. The easiest way to install pytest is with `pip3`: + +``` +pip3 install pytest +``` +### Run tests +First, cd in the build directory: + +``` +cd <name of the build directory> +``` + +You can easily run the test of all the components: + +``` +meson test +``` + +To list all available tests: + +``` +meson test --list +``` + +Meson also supports running the tests under GDB. Just doing this: + +``` +meson test --gdb <testname> +``` + +### Producing a coverage report + +First, configure the build with this command. + +``` +cd <name of the build directory> +meson configure -Db_coverage=true +``` + +Then issue the following commands. + +``` +meson test +ninja coverage-html +``` + +The coverage report can be found in the `meson-logs/coveragereport` subdirectory. + +## Documentation +### External dependencies +To generate the documentation you need the [Doxygen](https://www.doxygen.nl/index.html) program. +Optional you can install the "dot" tool from [graphviz](http://www.graphviz.org/) to generate more advanced diagrams and graphs. + +### Generate Documentation + +To generate the documentation you need to run: + +``` +cd <name of the build directory> +meson compile doc +``` + diff --git a/README.md b/README.md index 325a24c..cad1887 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ If you find a bug or have a feature request please file an [issue][1] or send me an [email][2]. -Compiled executables can be found [here][3]. +Compiled executables can be found [here][3]. The building instructions can be found [here](INSTALL.md). ## Usage @@ -86,7 +86,7 @@ You can find the user manual [here](doc). A simple example to show how the compression tool works. Instructions on how to perform compression without headers can be found [here](how_to_no_header.md). -0. Download the [tool][3] or run `make` to build the tool +0. Download the [tool][3] or [build the tool](INSTALL.md) yourself 1. Create a configuration file * Create a cfg directory -- GitLab From 52eaa596ee44d319f1a78d8019f21d5e6ff6b4d6 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Thu, 17 Feb 2022 16:50:36 +0100 Subject: [PATCH 12/46] move doxygen in a dir --- doc/doxygen/Doxyfile.in | 14 +++++++------- doc/doxygen/meson.build | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/doc/doxygen/Doxyfile.in b/doc/doxygen/Doxyfile.in index f4fba51..08f1e39 100644 --- a/doc/doxygen/Doxyfile.in +++ b/doc/doxygen/Doxyfile.in @@ -772,7 +772,7 @@ FILE_VERSION_FILTER = "/bin/sh -c 'git log --pretty=\"format:%ci, author:%aN # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. -LAYOUT_FILE = @SRCDIR@/doc/DoxygenLayout.xml +LAYOUT_FILE = @DOXYDIR@/DoxygenLayout.xml # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib @@ -1221,7 +1221,7 @@ HTML_FILE_EXTENSION = .html # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_HEADER = @SRCDIR@/doc/header.html +HTML_HEADER = @DOXYDIR@/header.html # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard @@ -1231,7 +1231,7 @@ HTML_HEADER = @SRCDIR@/doc/header.html # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_FOOTER = @SRCDIR@/doc/footer.html +HTML_FOOTER = @DOXYDIR@/footer.html # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of @@ -1256,7 +1256,7 @@ HTML_STYLESHEET = # list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_STYLESHEET = @SRCDIR@/doc/customdoxygen.css +HTML_EXTRA_STYLESHEET = @DOXYDIR@/customdoxygen.css # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note @@ -1266,9 +1266,9 @@ HTML_EXTRA_STYLESHEET = @SRCDIR@/doc/customdoxygen.css # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_FILES = @SRCDIR@/doc/bootstrap.min.css \ - @SRCDIR@/doc/bootstrap.min.js \ - @SRCDIR@/doc/doxy-boot.js +HTML_EXTRA_FILES = @DOXYDIR@/bootstrap.min.css \ + @DOXYDIR@/bootstrap.min.js \ + @DOXYDIR@/doxy-boot.js # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to diff --git a/doc/doxygen/meson.build b/doc/doxygen/meson.build index 33a3495..f7facf3 100644 --- a/doc/doxygen/meson.build +++ b/doc/doxygen/meson.build @@ -13,6 +13,7 @@ doc_layout_files = files([ cdata_doc = configuration_data() cdata_doc.set('VERSION', meson.project_version()) cdata_doc.set('SRCDIR', meson.project_source_root()) +cdata_doc.set('DOXYDIR', meson.current_source_dir()) cdata_doc.set('BUILDDIR', meson.project_build_root()) if find_program('dot', required : false).found() cdata_doc.set('HAVE_DOT', 'YES') -- GitLab From 8b9c39d5eba57a028486b2ddec920f14ec9da608 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Thu, 17 Feb 2022 16:52:46 +0100 Subject: [PATCH 13/46] add cross file --- mingw-w64-64.txt | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 mingw-w64-64.txt diff --git a/mingw-w64-64.txt b/mingw-w64-64.txt new file mode 100644 index 0000000..773be92 --- /dev/null +++ b/mingw-w64-64.txt @@ -0,0 +1,24 @@ +[binaries] +c = 'x86_64-w64-mingw32-gcc' +cpp = 'x86_64-w64-mingw32-g++' +objc = 'x86_64-w64-mingw32-gcc' +ar = 'x86_64-w64-mingw32-ar' +strip = 'x86_64-w64-mingw32-strip' +pkgconfig = 'x86_64-w64-mingw32-pkg-config' +windres = 'x86_64-w64-mingw32-windres' +exe_wrapper = 'wine64' +cmake = 'cmake' + +[host_machine] +system = 'windows' +cpu_family = 'x86_64' +cpu = 'x86_64' +endian = 'little' + +[cmake] + +CMAKE_BUILD_WITH_INSTALL_RPATH = 'ON' +CMAKE_FIND_ROOT_PATH_MODE_PROGRAM = 'NEVER' +CMAKE_FIND_ROOT_PATH_MODE_LIBRARY = 'ONLY' +CMAKE_FIND_ROOT_PATH_MODE_INCLUDE = 'ONLY' +CMAKE_FIND_ROOT_PATH_MODE_PACKAGE = 'ONLY' -- GitLab From bcf9c653a9740bd7d3ae095978458b291d43e536 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Tue, 1 Feb 2022 15:33:17 +0100 Subject: [PATCH 14/46] Adding the possibility to import non-imagette data --- cmp_tool.c | 9 +-- include/cmp_data_types.h | 42 +++++++---- include/cmp_io.h | 12 ++-- include/cmp_support.h | 1 + lib/cmp_data_types.c | 146 +++++++++++++++++++++++++++++++++++++++ lib/cmp_io.c | 1 + lib/cmp_support.c | 39 ++++++++++- 7 files changed, 225 insertions(+), 25 deletions(-) diff --git a/cmp_tool.c b/cmp_tool.c index 90aa1df..e3b2716 100755 --- a/cmp_tool.c +++ b/cmp_tool.c @@ -32,6 +32,7 @@ #include "cmp_guess.h" #include "cmp_entity.h" #include "rdcu_pkt_to_file.h" +#include "cmp_data_types.h" #define BUFFER_LENGTH_DEF_FAKTOR 2 @@ -189,7 +190,7 @@ int main(int argc, char **argv) verbose_en = 1; break; case 'V': /* --version */ - printf("%s version %s\n", PROGRAM_NAME, CMP_TOOL_VERSION); + printf("%s version %s\n", PROGRAM_NAME, VERSION); exit(EXIT_SUCCESS); break; case DIFF_CFG_OPTION: @@ -272,9 +273,9 @@ int main(int argc, char **argv) printf("#########################################################\n"); printf("### PLATO Compression/Decompression Tool Version %s ###\n", - CMP_TOOL_VERSION); + VERSION); printf("#########################################################\n"); - if (!strcmp(CMP_TOOL_VERSION, "0.07") || !strcmp(CMP_TOOL_VERSION, "0.08")) + if (!strcmp(VERSION, "0.07") || !strcmp(VERSION, "0.08")) printf("Info: Note that the behaviour of the cmp_tool has changed. From now on, the compressed data will be preceded by a header by default. The old behaviour can be achieved with the --no_header option.\n\n"); if (!data_file_name) { @@ -628,7 +629,7 @@ static int add_cmp_ent_hdr(struct cmp_cfg *cfg, struct cmp_info *info, cmp_bit_to_4byte(info->cmp_size)); ent = (struct cmp_entity *)cfg->icu_output_buf; - s = cmp_ent_build(ent, data_type, cmp_tool_gen_version_id(CMP_TOOL_VERSION), + s = cmp_ent_build(ent, data_type, cmp_tool_gen_version_id(VERSION), start_time, cmp_ent_create_timestamp(NULL), model_id, model_counter, info, cfg); if (!s) { diff --git a/include/cmp_data_types.h b/include/cmp_data_types.h index 345c0ae..2ec635e 100644 --- a/include/cmp_data_types.h +++ b/include/cmp_data_types.h @@ -33,6 +33,8 @@ #include <stdint.h> +#include "compiler.h" + #define MODE_RAW_S_FX 100 #define MODE_MODEL_ZERO_S_FX 101 #define MODE_DIFF_ZERO_S_FX 102 @@ -90,11 +92,25 @@ int lossy_rounding_32(uint32_t *data_buf, unsigned int samples, unsigned int int de_lossy_rounding_32(uint32_t *data_buf, uint32_t samples_used, uint32_t round_used); -/* @see PLATO-LESIA-PL-RP-0031 Issue: 1.9 (N-DPU->ICU data rate) */ -struct __attribute__((packed)) S_FX { +void cmp_input_big_to_cpu_endiannessy(void *data, u_int32_t data_size_byte, + uint32_t cmp_mode); + +/* @see for packed definition: PLATO-LESIA-PL-RP-0031 Issue: 1.9 (N-DPU->ICU data rate) */ +#define N_DPU_ICU_MULTI_ENTRY_HDR_SIZE 12 + +struct n_dpu_icu_multi_entry_hdr { + uint64_t ncxx_timestamp:48; + uint16_t ncxx_configuration_id; + uint16_t ncxx_collection_id; + uint16_t ncxx_collection_length; +} __attribute__((packed)); +compile_time_assert(sizeof(struct n_dpu_icu_multi_entry_hdr) == N_DPU_ICU_MULTI_ENTRY_HDR_SIZE, N_DPU_ICU_MULTI_ENTRY_HDR_SIZE_IS_NOT_CORRECT); + + +struct S_FX { uint8_t EXPOSURE_FLAGS; uint32_t FX; -}; +} __attribute__((packed)); struct S_FX sub_S_FX(struct S_FX a, struct S_FX b); struct S_FX add_S_FX(struct S_FX a, struct S_FX b); @@ -110,7 +126,7 @@ struct S_FX_EFX { uint8_t EXPOSURE_FLAGS; uint32_t FX; uint32_t EFX; -}__attribute__((packed)); +} __attribute__((packed)); struct S_FX_EFX sub_S_FX_EFX(struct S_FX_EFX a, struct S_FX_EFX b); struct S_FX_EFX add_S_FX_EFX(struct S_FX_EFX a, struct S_FX_EFX b); @@ -127,7 +143,7 @@ struct S_FX_NCOB { uint32_t FX; uint32_t NCOB_X; uint32_t NCOB_Y; -}__attribute__((packed)); +} __attribute__((packed)); struct S_FX_NCOB sub_S_FX_NCOB(struct S_FX_NCOB a, struct S_FX_NCOB b); struct S_FX_NCOB add_S_FX_NCOB(struct S_FX_NCOB a, struct S_FX_NCOB b); @@ -135,9 +151,9 @@ int lossy_rounding_S_FX_NCOB(struct S_FX_NCOB *data_buf, unsigned int samples, unsigned int round); int de_lossy_rounding_S_FX_NCOB(struct S_FX_NCOB *data_buf, unsigned int samples_used, unsigned int round_used); -struct S_FX_NCOB cal_up_model_S_FX_NCOB(struct S_FX_NCOB data_buf, struct - S_FX_NCOB model_buf, unsigned int - model_value); +struct S_FX_NCOB cal_up_model_S_FX_NCOB(struct S_FX_NCOB data_buf, + struct S_FX_NCOB model_buf, + unsigned int model_value); struct S_FX_EFX_NCOB_ECOB { @@ -148,7 +164,7 @@ struct S_FX_EFX_NCOB_ECOB { uint32_t EFX; uint32_t ECOB_X; uint32_t ECOB_Y; -}__attribute__((packed)); +} __attribute__((packed)); struct S_FX_EFX_NCOB_ECOB sub_S_FX_EFX_NCOB_ECOB(struct S_FX_EFX_NCOB_ECOB a, struct S_FX_EFX_NCOB_ECOB b); @@ -162,20 +178,20 @@ int de_lossy_rounding_S_FX_EFX_NCOB_ECOB(struct S_FX_EFX_NCOB_ECOB *data_buf, struct F_FX { uint32_t FX; -}__attribute__((packed)); +} __attribute__((packed)); struct F_FX_EFX { uint32_t FX; uint32_t EFX; -}__attribute__((packed)); +} __attribute__((packed)); struct F_FX_NCOB { uint32_t FX; uint32_t NCOB_X; uint32_t NCOB_Y; -}__attribute__((packed)); +} __attribute__((packed)); struct F_FX_EFX_NCOB_ECOB { @@ -185,6 +201,6 @@ struct F_FX_EFX_NCOB_ECOB { uint32_t EFX; uint32_t ECOB_X; uint32_t ECOB_Y; -}__attribute__((packed)); +} __attribute__((packed)); #endif /* CMP_DATA_TYPE_H */ diff --git a/include/cmp_io.h b/include/cmp_io.h index 179d59f..39cfe59 100644 --- a/include/cmp_io.h +++ b/include/cmp_io.h @@ -31,14 +31,12 @@ void print_help(const char *program_name); int read_cmp_cfg(const char *file_name, struct cmp_cfg *cfg, int verbose_en); int read_cmp_info(const char *file_name, struct cmp_info *info, int verbose_en); -ssize_t read_file8(const char *file_name, uint8_t *buf, uint32_t n_word, - int verbose_en); -ssize_t read_file16(const char *file_name, uint16_t *buf, uint32_t samples, - int verbose_en); -ssize_t read_file32(const char *file_name, uint32_t *buf, uint32_t samples, - int verbose_en); +ssize_t read_file_data(const char *file_name, unsigned int cmp_mode, void *buf, + uint32_t buf_size, int verbose_en); ssize_t read_file_cmp_entity(const char *file_name, struct cmp_entity *ent, - uint32_t ent_size, int verbose_en); + uint32_t ent_size, int verbose_en); +ssize_t read_file32(const char *file_name, uint32_t *buf, uint32_t buf_size, + int verbose_en); uint32_t cmp_tool_gen_version_id(const char *version); diff --git a/include/cmp_support.h b/include/cmp_support.h index 0c1c90f..cfe4bbd 100644 --- a/include/cmp_support.h +++ b/include/cmp_support.h @@ -179,6 +179,7 @@ uint32_t get_max_spill(unsigned int golomb_par, unsigned int cmp_mode); uint32_t cmp_get_good_spill(unsigned int golomb_par, unsigned int cmp_mode); size_t size_of_a_sample(unsigned int cmp_mode); +int cmp_input_size_to_samples(unsigned int size, unsigned int cmp_mode); unsigned int cmp_bit_to_4byte(unsigned int cmp_size_bit); unsigned int cmp_cal_size_of_data(unsigned int samples, unsigned int cmp_mode); diff --git a/lib/cmp_data_types.c b/lib/cmp_data_types.c index 1df363c..cf8c08c 100644 --- a/lib/cmp_data_types.c +++ b/lib/cmp_data_types.c @@ -20,6 +20,7 @@ #include "../include/cmp_data_types.h" #include "../include/cmp_support.h" #include "../include/cmp_debug.h" +#include "../include/byteorder.h" /** @@ -96,6 +97,15 @@ int de_lossy_rounding_16(uint16_t *data_buf, uint32_t samples_used, uint32_t } +static void be_to_cpus_16(uint16_t *a, uint32_t samples) +{ + size_t i; + + for (i = 0; i < samples; ++i) + be16_to_cpus(&a[i]); +} + + /** * @brief rounding down the least significant digits of a uint32_t data buffer * @@ -261,6 +271,15 @@ struct S_FX cal_up_model_S_FX(struct S_FX data_buf, struct S_FX model_buf, } +void be_to_cpus_S_FX(struct S_FX *a, uint32_t samples) +{ + size_t i; + + for (i = 0; i < samples; ++i) + a[i].FX = be32_to_cpu(a[i].FX); +} + + struct S_FX_EFX sub_S_FX_EFX(struct S_FX_EFX a, struct S_FX_EFX b) { struct S_FX_EFX result; @@ -371,6 +390,17 @@ struct S_FX_EFX cal_up_model_S_FX_EFX(struct S_FX_EFX data_buf, struct S_FX_EFX } +void be_to_cpus_S_FX_EFX(struct S_FX_EFX *a, uint32_t samples) +{ + size_t i; + + for (i = 0; i < samples; ++i) { + a[i].FX = be32_to_cpu(a[i].FX); + a[i].EFX = be32_to_cpu(a[i].EFX); + } +} + + struct S_FX_NCOB sub_S_FX_NCOB(struct S_FX_NCOB a, struct S_FX_NCOB b) { struct S_FX_NCOB result; @@ -490,6 +520,18 @@ struct S_FX_NCOB cal_up_model_S_FX_NCOB(struct S_FX_NCOB data_buf, struct S_FX_N } +void be_to_cpus_S_FX_NCOB(struct S_FX_NCOB *a, uint32_t samples) +{ + size_t i; + + for (i = 0; i < samples; ++i) { + a[i].FX = be32_to_cpu(a[i].FX); + a[i].NCOB_X = be32_to_cpu(a[i].NCOB_X); + a[i].NCOB_Y = be32_to_cpu(a[i].NCOB_Y); + } +} + + struct S_FX_EFX_NCOB_ECOB sub_S_FX_EFX_NCOB_ECOB(struct S_FX_EFX_NCOB_ECOB a, struct S_FX_EFX_NCOB_ECOB b) { @@ -638,3 +680,107 @@ struct S_FX_EFX_NCOB_ECOB cal_up_model_S_FX_EFX_NCOB_ECOB return result; } + + +void be_to_cpus_S_FX_EFX_NCOB_ECOB(struct S_FX_EFX_NCOB_ECOB *a, uint32_t samples) +{ + size_t i; + + for (i = 0; i < samples; ++i) { + a[i].FX = be32_to_cpu(a[i].FX); + a[i].NCOB_X = be32_to_cpu(a[i].NCOB_X); + a[i].NCOB_Y = be32_to_cpu(a[i].NCOB_Y); + a[i].EFX = be32_to_cpu(a[i].EFX); + a[i].ECOB_X = be32_to_cpu(a[i].ECOB_X); + a[i].ECOB_Y = be32_to_cpu(a[i].ECOB_Y); + } +} + + +/** + * @brief swap the endianness of science products form big endian to the cpu + * endianness in place + * + * @param data pointer to a data sample + * @param cmp_mode compression mode + */ + +void cmp_input_big_to_cpu_endiannessy(void *data, u_int32_t data_size_byte, + uint32_t cmp_mode) +{ + int samples = cmp_input_size_to_samples(data_size_byte, cmp_mode); + + if (!data) + return; + + if (samples < 0) { + debug_print("Error: Can not convert data size in samples.\n"); + return; + } + + if (!rdcu_supported_mode_is_used(cmp_mode)) + data = (uint8_t *)data + N_DPU_ICU_MULTI_ENTRY_HDR_SIZE; + + switch (cmp_mode) { + case MODE_RAW: + case MODE_MODEL_ZERO: + case MODE_MODEL_MULTI: + case MODE_DIFF_ZERO: + case MODE_DIFF_MULTI: + be_to_cpus_16(data, samples); + break; + case MODE_RAW_S_FX: + case MODE_MODEL_ZERO_S_FX: + case MODE_MODEL_MULTI_S_FX: + case MODE_DIFF_ZERO_S_FX: + case MODE_DIFF_MULTI_S_FX: + be_to_cpus_S_FX(data, samples); + break; + case MODE_MODEL_ZERO_S_FX_EFX: + case MODE_MODEL_MULTI_S_FX_EFX: + case MODE_DIFF_ZERO_S_FX_EFX: + case MODE_DIFF_MULTI_S_FX_EFX: + be_to_cpus_S_FX_EFX(data, samples); + break; + case MODE_MODEL_ZERO_S_FX_EFX_NCOB_ECOB: + case MODE_MODEL_MULTI_S_FX_EFX_NCOB_ECOB: + case MODE_DIFF_ZERO_S_FX_EFX_NCOB_ECOB: + case MODE_DIFF_MULTI_S_FX_EFX_NCOB_ECOB: + be_to_cpus_S_FX_EFX_NCOB_ECOB(data, samples); + break; + case MODE_MODEL_ZERO_F_FX: + case MODE_MODEL_MULTI_F_FX: + case MODE_DIFF_ZERO_F_FX: + case MODE_DIFF_MULTI_F_FX: + /* be_to_cpus_F_FX(data, samples); */ + /* break; */ + case MODE_MODEL_ZERO_F_FX_EFX: + case MODE_MODEL_MULTI_F_FX_EFX: + case MODE_DIFF_ZERO_F_FX_EFX: + case MODE_DIFF_MULTI_F_FX_EFX: + /* be_to_cpus_F_FX_EFX(data, samples); */ + /* break; */ + case MODE_MODEL_ZERO_F_FX_NCOB: + case MODE_MODEL_MULTI_F_FX_NCOB: + case MODE_DIFF_ZERO_F_FX_NCOB: + case MODE_DIFF_MULTI_F_FX_NCOB: + /* be_to_cpus_F_FX_NCOB(data, samples); */ + /* break; */ + case MODE_MODEL_ZERO_F_FX_EFX_NCOB_ECOB: + case MODE_MODEL_MULTI_F_FX_EFX_NCOB_ECOB: + case MODE_DIFF_ZERO_F_FX_EFX_NCOB_ECOB: + case MODE_DIFF_MULTI_F_FX_EFX_NCOB_ECOB: + /* be_to_cpus_F_FX_EFX_NCOB_ECOB(data, samples); */ + /* break; */ + case MODE_RAW_32: + case MODE_MODEL_ZERO_32: + case MODE_MODEL_MULTI_32: + case MODE_DIFF_ZERO_32: + case MODE_DIFF_MULTI_32: + /* be32_to_cpus(data); */ + /* break; */ + default: + debug_print("Error: Compression mode not supported.\n"); + break; + } +} diff --git a/lib/cmp_io.c b/lib/cmp_io.c index 9dc9ba1..7c00d56 100644 --- a/lib/cmp_io.c +++ b/lib/cmp_io.c @@ -29,6 +29,7 @@ #include "cmp_support.h" #include "rdcu_cmd.h" #include "byteorder.h" +#include "cmp_data_types.h" /** diff --git a/lib/cmp_support.c b/lib/cmp_support.c index a945971..93db7b6 100644 --- a/lib/cmp_support.c +++ b/lib/cmp_support.c @@ -558,6 +558,36 @@ size_t size_of_a_sample(unsigned int cmp_mode) } +/** + * @brief calculates the number of samples for a given data size for the + * different compression modes + * + * @param size size of the data me + * @param cmp_mode compression mode + * + * @returns the number samples for the given compression mode; negative on error + */ + +int cmp_input_size_to_samples(unsigned int size, unsigned int cmp_mode) +{ + unsigned int samples_size = size_of_a_sample(cmp_mode); + + if (!samples_size) + return -1; + + if (!rdcu_supported_mode_is_used(cmp_mode)) { + if (size < N_DPU_ICU_MULTI_ENTRY_HDR_SIZE) + return -1; + size -= N_DPU_ICU_MULTI_ENTRY_HDR_SIZE; + } + + if (size % samples_size) + return -1; + + return size/samples_size; +} + + /** * @brief calculate the need bytes to hold a bitstream * @@ -579,12 +609,19 @@ unsigned int cmp_bit_to_4byte(unsigned int cmp_size_bit) * @param samples number of data samples * @param cmp_mode used compression mode * + * @note for non RDCU modes the N_DPU ICU multi entry header size is added + * * @returns the size in bytes to store the data sample */ unsigned int cmp_cal_size_of_data(unsigned int samples, unsigned int cmp_mode) { - return samples * size_of_a_sample(cmp_mode); + unsigned int s = samples * size_of_a_sample(cmp_mode); + + if (!rdcu_supported_mode_is_used(cmp_mode)) + s += N_DPU_ICU_MULTI_ENTRY_HDR_SIZE; + + return s; } -- GitLab From 9ca41d9cd7f9301b096121ff63c0aea1fefcf24e Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Wed, 2 Feb 2022 10:24:13 +0100 Subject: [PATCH 15/46] correct documentation --- lib/cmp_entity.c | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/cmp_entity.c b/lib/cmp_entity.c index 84f784d..99aa3bd 100644 --- a/lib/cmp_entity.c +++ b/lib/cmp_entity.c @@ -1640,7 +1640,6 @@ void *cmp_ent_get_data_buf(struct cmp_entity *ent) * @param data_buf pointer where the destination data buffer where the * compressed data is copied to (can be NULL) * @param data_buf_size size of the destination data buffer - * @param verbose_en print verbose output if not zero * * @returns the size in bytes to store the compressed data; negative on error * -- GitLab From 95a779ee529b28d0c71384a4c2f38c85c62cce10 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Tue, 15 Mar 2022 20:14:27 +0100 Subject: [PATCH 16/46] Refactor put_n_bits() --- lib/cmp_icu_new.c | 165 ++++++++++ subprojects/unity.wrap | 8 + test/cmp_icu/test_cmp_icu_new.c | 373 +++++++++++++++++++++ test/tools/generate_test_runner.rb | 512 +++++++++++++++++++++++++++++ test/tools/meson.build | 6 + test/tools/run_test.erb | 37 +++ test/tools/type_sanitizer.rb | 6 + 7 files changed, 1107 insertions(+) create mode 100644 lib/cmp_icu_new.c create mode 100644 subprojects/unity.wrap create mode 100644 test/cmp_icu/test_cmp_icu_new.c create mode 100755 test/tools/generate_test_runner.rb create mode 100644 test/tools/meson.build create mode 100644 test/tools/run_test.erb create mode 100644 test/tools/type_sanitizer.rb diff --git a/lib/cmp_icu_new.c b/lib/cmp_icu_new.c new file mode 100644 index 0000000..e6f6752 --- /dev/null +++ b/lib/cmp_icu_new.c @@ -0,0 +1,165 @@ +/** + * @file icu_cmp.c + * @author Dominik Loidolt (dominik.loidolt@univie.ac.at), + * @date 2020 + * + * @copyright GPLv2 + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * @brief software compression library + * @see Data Compression User Manual PLATO-UVIE-PL-UM-0001 + */ + + +#include <stdint.h> + +#include "cmp_debug.h" + + +/* return code if the bitstream buffer is too small to store the whole bitstream */ +#define CMP_ERROR_SAMLL_BUF -2 + + +/** + * @brief put the value of up to 32 bits into a bitstream accessed as 32-bit + * RAM in big-endian + * + * @param value the value to put + * @param n_bits number of bits to put in the bitstream + * @param bit_offset bit index where the bits will be put, seen from the very + * beginning of the bitstream + * @param bitstream_adr this is the pointer to the beginning of the bitstream + * (can be NULL) + * @param max_bit_len maximum length of the bitstream in bits; is ignored if + * bitstream_adr is NULL + * + * @returns length in bits of the generated bitstream on success; returns + * negative in case of erroneous input; returns CMP_ERROR_SAMLL_BUF if + * the bitstream buffer is too small to put the value in the bitstream + * @note a value with more bits set as the n_bits parameter is considered as an + * erroneous input. + */ + +static int put_n_bits32(uint32_t value, unsigned int n_bits, int bit_offset, + uint32_t *bitstream_adr, unsigned int max_bit_len) +{ + uint32_t *local_adr; + uint32_t mask; + unsigned int shiftRight, shiftLeft, bitsLeft, bitsRight; + int stream_len = (int)(n_bits + (unsigned int)bit_offset); /* overflow results in a negative return value */ + + /* leave in case of erroneous input */ + if (bit_offset < 0) + return -1; + + if (n_bits == 0) + return 0; + + if (n_bits > 32) + return -1; + + /* (M) is the n_bits parameter large enough to cover all value bits; the + * calculations can be re-used in the unsegmented code, so we have no overhead + */ + shiftRight = 32 - n_bits; + mask = 0xFFFFFFFFU >> shiftRight; + if (value & ~mask) { + debug_print("Error: Not all set bits in the put value are added to the bitstream. Check value n_bits parameter combination.\n"); + return -1; + } + + /* Do we need to write data to the bitstream? */ + if (!bitstream_adr) + return stream_len; + + /* Check if bitstream buffer is large enough */ + if ((unsigned int)stream_len > max_bit_len) { + debug_print("Error: The buffer for the compressed data is too small to hold the compressed data. Try a larger buffer_length parameter.\n"); + return CMP_ERROR_SAMLL_BUF; + } + + /* Separate the bit_offset into word offset (set local_adr pointer) and local bit offset (bitsLeft) */ + local_adr = bitstream_adr + (bit_offset >> 5); + bitsLeft = bit_offset & 0x1F; + + /* Calculate the bitsRight for the unsegmented case. If bitsRight is + * negative we need to split the value over two words + */ + bitsRight = shiftRight - bitsLeft; + + if ((int)bitsRight >= 0) { + /* UNSEGMENTED + * + *|-----------|XXXXX|----------------| + * bitsLeft n bitsRight + * + * -> to get the mask: + * shiftRight = bitsLeft + bitsRight = 32 - n + * shiftLeft = bitsRight = 32 - n - bitsLeft = shiftRight - bitsLeft + */ + + shiftLeft = bitsRight; + + /* generate the mask, the bits for the values will be true + * shiftRight = 32 - n_bits; see (M) above! + * mask = (0XFFFFFFFF >> shiftRight) << shiftLeft; see (M) above! + */ + mask <<= shiftLeft; + value <<= shiftLeft; + + /* clear the destination with inverse mask */ + *(local_adr) &= ~mask; + + /* assign the value */ + *(local_adr) |= value; + + } else { + /* SEGMENTED + * + *|-----------------------------|XXX| |XX|------------------------------| + * bitsLeft n1 n2 bitsRight + * + * -> to get the mask part 1: + * shiftRight = bitsLeft + * n1 = n - (bitsLeft + n - 32) = 32 - bitsLeft + * + * -> to get the mask part 2: + * n2 = bitsLeft + n - 32 = -(32 - n - bitsLeft) = -(bitsRight_UNSEGMENTED) + * shiftLeft = 32 - n2 = 32 - (bitsLeft + n - 32) = 64 - bitsLeft - n + * + */ + + unsigned int n2 = -bitsRight; + + /* part 1: */ + shiftRight = bitsLeft; + mask = 0XFFFFFFFFU >> shiftRight; + + /* clear the destination with inverse mask */ + *(local_adr) &= ~mask; + + /* assign the value part 1 */ + *(local_adr) |= (value >> n2); + + /* part 2: */ + /* adjust address */ + local_adr += 1; + shiftLeft = 32 - n2; + mask = 0XFFFFFFFFU << shiftLeft; + + /* clear the destination with inverse mask */ + *(local_adr) &= ~mask; + + /* assign the value part 2 */ + *(local_adr) |= (value << shiftLeft); + } + return stream_len; +} + diff --git a/subprojects/unity.wrap b/subprojects/unity.wrap new file mode 100644 index 0000000..8cc4883 --- /dev/null +++ b/subprojects/unity.wrap @@ -0,0 +1,8 @@ +[wrap-file] +directory = Unity-2.5.2 +source_url = https://github.com/ThrowTheSwitch/Unity/archive/refs/tags/v2.5.2.tar.gz +source_filename = Unity-2.5.2.tar.gz +source_hash = 3786de6c8f389be3894feae4f7d8680a02e70ed4dbcce36109c8f8646da2671a + +[provide] +unity = unity_dep diff --git a/test/cmp_icu/test_cmp_icu_new.c b/test/cmp_icu/test_cmp_icu_new.c new file mode 100644 index 0000000..6e57615 --- /dev/null +++ b/test/cmp_icu/test_cmp_icu_new.c @@ -0,0 +1,373 @@ +#include <string.h> + +#include "unity.h" + +/* this is a hack to test static functions */ +#include "../lib/cmp_icu_new.c" + + + +/** + * @test put_n_bits32 + */ + +#define SDP_PB_N 3 + + +static void init_PB32_arrays(uint32_t *z, uint32_t *o) +{ + uint32_t i; + + /* init testarray with all 0 and all 1 */ + for (i = 0; i < SDP_PB_N; i++) { + z[i] = 0; + o[i] = 0xffffffff; + } +} + + +void test_put_n_bits32(void) +{ + uint32_t v, n; + int o, rval; /* return value */ + uint32_t testarray0[SDP_PB_N]; + uint32_t testarray1[SDP_PB_N]; + const uint32_t l = sizeof(testarray0) * CHAR_BIT; + + /* hereafter, the value is v, + * the number of bits to write is n, + * the offset of the bit is o, + * the max length the bitstream in bits is l + */ + + init_PB32_arrays(testarray0, testarray1); + TEST_ASSERT(testarray0[0] == 0); + TEST_ASSERT(testarray1[0] == 0xffffffff); + + /*** n=0 ***/ + + /* do not write, left border */ + v = 0; n = 0; o = 0; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(0, rval); + TEST_ASSERT(testarray0[0] == 0); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(0, rval); + TEST_ASSERT(testarray1[0] == 0xffffffff); + + /* TODO: not a valid test */ + v = 0xffffffff; n = 0; o = 0; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(0, rval); + TEST_ASSERT(testarray0[0] == 0); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(0, rval); + TEST_ASSERT(testarray1[0] == 0xffffffff); + + /* do not write, right border */ + v = 0; n = 0; o = 31; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(0, rval); + TEST_ASSERT(testarray0[0] == 0); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(0, rval); + TEST_ASSERT(testarray1[0] == 0xffffffff); + + /* TODO: not a valid test */ + /* test value = 0xffffffff; N = 0 */ + v = 0xffffffff; n = 0; o = 31; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(0, rval); + TEST_ASSERT(testarray0[0] == 0); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(0, rval); + TEST_ASSERT(testarray1[0] == 0xffffffff); + + /*** n=1 ***/ + + /* left border, write 0 */ + v = 0; n = 1; o = 0; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(1, rval); + TEST_ASSERT(testarray0[0] == 0); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(1, rval); + TEST_ASSERT(testarray1[0] == 0x7fffffff); + + /* left border, write 1 */ + v = 1; n = 1; o = 0; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(1, rval); + TEST_ASSERT(testarray0[0] == 0x80000000); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(1, rval); + TEST_ASSERT(testarray1[0] == 0xffffffff); + + /* left border, write 32 */ + v = 0xf0f0abcd; n = 32; o = 0; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 32); + TEST_ASSERT(testarray0[0] == 0xf0f0abcd); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(rval, 32); + TEST_ASSERT(testarray1[0] == 0xf0f0abcd); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* middle, write 2 bits */ + v = 3; n = 2; o = 29; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 31); + TEST_ASSERT(testarray0[0] == 0x6); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT(testarray1[0] == 0xffffffff); + TEST_ASSERT_EQUAL_INT(rval, 31); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /*** n=5, unsegmented ***/ + + /* left border, write 0 */ + v = 0; n = 5; o = 0; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 5); + TEST_ASSERT(testarray0[0] == 0); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT(testarray1[0] == 0x07ffffff); + TEST_ASSERT_EQUAL_INT(rval, 5); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* left border, write 11111 */ + v = 0x1f; n = 5; o = 0; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 5); + TEST_ASSERT(testarray0[0] == 0xf8000000); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(rval, 5); + TEST_ASSERT(testarray1[0] == 0xffffffff); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* middle, write 0 */ + v = 0; n = 5; o = 7; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 12); + TEST_ASSERT(testarray0[0] == 0); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(rval, 12); + TEST_ASSERT(testarray1[0] == 0xfe0fffff); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* middle, write 11111 */ + v = 0x1f; n = 5; o = 7; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 12); + TEST_ASSERT(testarray0[0] == 0x01f00000); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(rval, 12); + TEST_ASSERT(testarray1[0] == 0xffffffff); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* right, write 0 */ + v = 0; n = 5; o = 91; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 96); + TEST_ASSERT(testarray0[0] == 0); + TEST_ASSERT(testarray0[1] == 0); + TEST_ASSERT(testarray0[0] == 0); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(rval, 96); + TEST_ASSERT(testarray1[0] == 0xffffffff); + TEST_ASSERT(testarray1[1] == 0xffffffff); + TEST_ASSERT(testarray1[2] == 0xffffffe0); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* right, write 11111 */ + v = 0x1f; n = 5; o = 91; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 96); + TEST_ASSERT(testarray0[0] == 0); + TEST_ASSERT(testarray0[1] == 0); + TEST_ASSERT(testarray0[2] == 0x0000001f); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(rval, 96); + TEST_ASSERT(testarray1[0] == 0xffffffff); + TEST_ASSERT(testarray1[1] == 0xffffffff); + TEST_ASSERT(testarray1[2] == 0xffffffff); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* 32 bit, write 0 */ + v = 0; n = 32; o = 0; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 32); + TEST_ASSERT(testarray0[0] == 0x00000000); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(rval, 32); + TEST_ASSERT(testarray1[0] == 0x00000000); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* 32 bit, write -1 */ + v = 0xffffffff; n = 32; o = 0; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 32); + TEST_ASSERT(testarray0[0] == 0xffffffff); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(rval, 32); + TEST_ASSERT(testarray1[0] == 0xffffffff); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* SEGMENTED cases */ + /* 5 bit, write 0 */ + v = 0; n = 5; o = 62; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 67); + TEST_ASSERT(testarray0[0] == 0); + TEST_ASSERT(testarray0[1] == 0); + TEST_ASSERT(testarray0[2] == 0); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(rval, 67); + TEST_ASSERT(testarray1[0] == 0xffffffff); + TEST_ASSERT(testarray1[1] == 0xfffffffc); + TEST_ASSERT(testarray1[2] == 0x1fffffff); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* 5 bit, write 1f */ + v = 0x1f; n = 5; o = 62; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 67); + TEST_ASSERT(testarray0[0] == 0); + TEST_ASSERT(testarray0[1] == 3); + TEST_ASSERT(testarray0[2] == 0xe0000000); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(rval, 67); + TEST_ASSERT(testarray1[0] == 0xffffffff); + TEST_ASSERT(testarray1[1] == 0xffffffff); + TEST_ASSERT(testarray1[2] == 0xffffffff); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* 32 bit, write 0 */ + v = 0; n = 32; o = 1; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 33); + TEST_ASSERT(testarray0[0] == 0x00000000); + TEST_ASSERT(testarray0[1] == 0x00000000); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(rval, 33); + TEST_ASSERT(testarray1[0] == 0x80000000); + TEST_ASSERT(testarray1[1] == 0x7fffffff); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* 32 bit, write -1 */ + v = 0xffffffff; n = 32; o = 1; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 33); + TEST_ASSERT(testarray0[0] == 0x7fffffff); + TEST_ASSERT(testarray0[1] == 0x80000000); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(rval, 33); + TEST_ASSERT(testarray1[0] == 0xffffffff); + TEST_ASSERT(testarray1[1] == 0xffffffff); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* test NULL buffer */ + v = 0; n = 0; o = 0; + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(rval, 0); + + v = 0; n = 1; o = 0; + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(rval, 1); + + v = 0; n = 5; o = 31; + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(rval, 36); + + v = 0; n = 2; o = 95; + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(rval, 97); /* rval can be longer than l */ + + /* error cases */ + /* n too large */ + v = 0x0; n = 33; o = 1; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, -1); + TEST_ASSERT(testarray0[0] == 0); + TEST_ASSERT(testarray0[1] == 0); + + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(rval, -1); + + /* value larger than n allows */ + v = 0x7f; n = 6; o = 10; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(-1, rval); + TEST_ASSERT(testarray0[0] == 0); + TEST_ASSERT(testarray0[1] == 0); + + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(-1, rval); + + /* try to put too much in the bitstream */ + v = 0x1; n = 1; o = 96; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, rval); + TEST_ASSERT(testarray0[0] == 0); + TEST_ASSERT(testarray0[1] == 0); + TEST_ASSERT(testarray0[2] == 0); + + /* this should work */ + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(97, rval); + + /* offset lager than max_bit_len */ + v = 0x0; n = 32; o = INT32_MAX; + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, rval); + TEST_ASSERT(testarray1[0] == 0xffffffff); + TEST_ASSERT(testarray1[1] == 0xffffffff); + TEST_ASSERT(testarray1[2] == 0xffffffff); + + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT(rval < 0); + + /* negative offset */ + v = 0x0; n = 0; o = -1; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(-1, rval); + TEST_ASSERT(testarray0[0] == 0); + TEST_ASSERT(testarray0[1] == 0); + + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(-1, rval); +} diff --git a/test/tools/generate_test_runner.rb b/test/tools/generate_test_runner.rb new file mode 100755 index 0000000..1c0ec34 --- /dev/null +++ b/test/tools/generate_test_runner.rb @@ -0,0 +1,512 @@ +#!/usr/bin/env ruby +# ========================================== +# Unity Project - A Test Framework for C +# Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams +# [Released under MIT License. Please refer to license.txt for details] +# ========================================== + +class UnityTestRunnerGenerator + def initialize(options = nil) + @options = UnityTestRunnerGenerator.default_options + case options + when NilClass + @options + when String + @options.merge!(UnityTestRunnerGenerator.grab_config(options)) + when Hash + # Check if some of these have been specified + @options[:has_setup] = !options[:setup_name].nil? + @options[:has_teardown] = !options[:teardown_name].nil? + @options[:has_suite_setup] = !options[:suite_setup].nil? + @options[:has_suite_teardown] = !options[:suite_teardown].nil? + @options.merge!(options) + else + raise 'If you specify arguments, it should be a filename or a hash of options' + end + require_relative 'type_sanitizer' + end + + def self.default_options + { + includes: [], + defines: [], + plugins: [], + framework: :unity, + test_prefix: 'test|spec|should', + mock_prefix: 'Mock', + mock_suffix: '', + setup_name: 'setUp', + teardown_name: 'tearDown', + test_reset_name: 'resetTest', + test_verify_name: 'verifyTest', + main_name: 'main', # set to :auto to automatically generate each time + main_export_decl: '', + cmdline_args: false, + omit_begin_end: false, + use_param_tests: false, + include_extensions: '(?:hpp|hh|H|h)', + source_extensions: '(?:cpp|cc|ino|C|c)' + } + end + + def self.grab_config(config_file) + options = default_options + unless config_file.nil? || config_file.empty? + require 'yaml' + yaml_guts = YAML.load_file(config_file) + options.merge!(yaml_guts[:unity] || yaml_guts[:cmock]) + raise "No :unity or :cmock section found in #{config_file}" unless options + end + options + end + + def run(input_file, output_file, options = nil) + @options.merge!(options) unless options.nil? + + # pull required data from source file + source = File.read(input_file) + source = source.force_encoding('ISO-8859-1').encode('utf-8', replace: nil) + tests = find_tests(source) + headers = find_includes(source) + testfile_includes = (headers[:local] + headers[:system]) + used_mocks = find_mocks(testfile_includes) + testfile_includes = (testfile_includes - used_mocks) + testfile_includes.delete_if { |inc| inc =~ /(unity|cmock)/ } + find_setup_and_teardown(source) + + # build runner file + generate(input_file, output_file, tests, used_mocks, testfile_includes) + + # determine which files were used to return them + all_files_used = [input_file, output_file] + all_files_used += testfile_includes.map { |filename| filename + '.c' } unless testfile_includes.empty? + all_files_used += @options[:includes] unless @options[:includes].empty? + all_files_used += headers[:linkonly] unless headers[:linkonly].empty? + all_files_used.uniq + end + + def generate(input_file, output_file, tests, used_mocks, testfile_includes) + File.open(output_file, 'w') do |output| + create_header(output, used_mocks, testfile_includes) + create_externs(output, tests, used_mocks) + create_mock_management(output, used_mocks) + create_setup(output) + create_teardown(output) + create_suite_setup(output) + create_suite_teardown(output) + create_reset(output) + create_run_test(output) unless tests.empty? + create_args_wrappers(output, tests) + create_main(output, input_file, tests, used_mocks) + end + + return unless @options[:header_file] && !@options[:header_file].empty? + + File.open(@options[:header_file], 'w') do |output| + create_h_file(output, @options[:header_file], tests, testfile_includes, used_mocks) + end + end + + def find_tests(source) + tests_and_line_numbers = [] + + # contains characters which will be substituted from within strings, doing + # this prevents these characters from interfering with scrubbers + # @ is not a valid C character, so there should be no clashes with files genuinely containing these markers + substring_subs = { '{' => '@co@', '}' => '@cc@', ';' => '@ss@', '/' => '@fs@' } + substring_re = Regexp.union(substring_subs.keys) + substring_unsubs = substring_subs.invert # the inverse map will be used to fix the strings afterwords + substring_unsubs['@quote@'] = '\\"' + substring_unsubs['@apos@'] = '\\\'' + substring_unre = Regexp.union(substring_unsubs.keys) + source_scrubbed = source.clone + source_scrubbed = source_scrubbed.gsub(/\\"/, '@quote@') # hide escaped quotes to allow capture of the full string/char + source_scrubbed = source_scrubbed.gsub(/\\'/, '@apos@') # hide escaped apostrophes to allow capture of the full string/char + source_scrubbed = source_scrubbed.gsub(/("[^"\n]*")|('[^'\n]*')/) { |s| s.gsub(substring_re, substring_subs) } # temporarily hide problematic characters within strings + source_scrubbed = source_scrubbed.gsub(/\/\/(?:.+\/\*|\*(?:$|[^\/])).*$/, '') # remove line comments that comment out the start of blocks + source_scrubbed = source_scrubbed.gsub(/\/\*.*?\*\//m, '') # remove block comments + source_scrubbed = source_scrubbed.gsub(/\/\/.*$/, '') # remove line comments (all that remain) + lines = source_scrubbed.split(/(^\s*\#.*$) | (;|\{|\}) /x) # Treat preprocessor directives as a logical line. Match ;, {, and } as end of lines + .map { |line| line.gsub(substring_unre, substring_unsubs) } # unhide the problematic characters previously removed + + lines.each_with_index do |line, _index| + # find tests + next unless line =~ /^((?:\s*(?:TEST_CASE|TEST_RANGE)\s*\(.*?\)\s*)*)\s*void\s+((?:#{@options[:test_prefix]}).*)\s*\(\s*(.*)\s*\)/m + + arguments = Regexp.last_match(1) + name = Regexp.last_match(2) + call = Regexp.last_match(3) + params = Regexp.last_match(4) + args = nil + + if @options[:use_param_tests] && !arguments.empty? + args = [] + arguments.scan(/\s*TEST_CASE\s*\((.*)\)\s*$/) { |a| args << a[0] } + + arguments.scan(/\s*TEST_RANGE\s*\((.*)\)\s*$/).flatten.each do |range_str| + args += range_str.scan(/\[(-?\d+.?\d*), *(-?\d+.?\d*), *(-?\d+.?\d*)\]/).map do |arg_values_str| + arg_values_str.map do |arg_value_str| + arg_value_str.include?('.') ? arg_value_str.to_f : arg_value_str.to_i + end + end.map do |arg_values| + (arg_values[0]..arg_values[1]).step(arg_values[2]).to_a + end.reduce do |result, arg_range_expanded| + result.product(arg_range_expanded) + end.map do |arg_combinations| + arg_combinations.flatten.join(', ') + end + end + end + + tests_and_line_numbers << { test: name, args: args, call: call, params: params, line_number: 0 } + end + + tests_and_line_numbers.uniq! { |v| v[:test] } + + # determine line numbers and create tests to run + source_lines = source.split("\n") + source_index = 0 + tests_and_line_numbers.size.times do |i| + source_lines[source_index..-1].each_with_index do |line, index| + next unless line =~ /\s+#{tests_and_line_numbers[i][:test]}(?:\s|\()/ + + source_index += index + tests_and_line_numbers[i][:line_number] = source_index + 1 + break + end + end + + tests_and_line_numbers + end + + def find_includes(source) + # remove comments (block and line, in three steps to ensure correct precedence) + source.gsub!(/\/\/(?:.+\/\*|\*(?:$|[^\/])).*$/, '') # remove line comments that comment out the start of blocks + source.gsub!(/\/\*.*?\*\//m, '') # remove block comments + source.gsub!(/\/\/.*$/, '') # remove line comments (all that remain) + + # parse out includes + includes = { + local: source.scan(/^\s*#include\s+\"\s*(.+\.#{@options[:include_extensions]})\s*\"/).flatten, + system: source.scan(/^\s*#include\s+<\s*(.+)\s*>/).flatten.map { |inc| "<#{inc}>" }, + linkonly: source.scan(/^TEST_FILE\(\s*\"\s*(.+\.#{@options[:source_extensions]})\s*\"/).flatten + } + includes + end + + def find_mocks(includes) + mock_headers = [] + includes.each do |include_path| + include_file = File.basename(include_path) + mock_headers << include_path if include_file =~ /^#{@options[:mock_prefix]}.*#{@options[:mock_suffix]}\.h$/i + end + mock_headers + end + + def find_setup_and_teardown(source) + @options[:has_setup] = source =~ /void\s+#{@options[:setup_name]}\s*\(/ + @options[:has_teardown] = source =~ /void\s+#{@options[:teardown_name]}\s*\(/ + @options[:has_suite_setup] ||= (source =~ /void\s+suiteSetUp\s*\(/) + @options[:has_suite_teardown] ||= (source =~ /int\s+suiteTearDown\s*\(int\s+([a-zA-Z0-9_])+\s*\)/) + end + + def create_header(output, mocks, testfile_includes = []) + output.puts('/* AUTOGENERATED FILE. DO NOT EDIT. */') + output.puts("\n/*=======Automagically Detected Files To Include=====*/") + output.puts("#include \"#{@options[:framework]}.h\"") + output.puts('#include "cmock.h"') unless mocks.empty? + if @options[:defines] && !@options[:defines].empty? + @options[:defines].each { |d| output.puts("#ifndef #{d}\n#define #{d}\n#endif /* #{d} */") } + end + if @options[:header_file] && !@options[:header_file].empty? + output.puts("#include \"#{File.basename(@options[:header_file])}\"") + else + @options[:includes].flatten.uniq.compact.each do |inc| + output.puts("#include #{inc.include?('<') ? inc : "\"#{inc}\""}") + end + testfile_includes.each do |inc| + output.puts("#include #{inc.include?('<') ? inc : "\"#{inc}\""}") + end + end + mocks.each do |mock| + output.puts("#include \"#{mock}\"") + end + output.puts('#include "CException.h"') if @options[:plugins].include?(:cexception) + + return unless @options[:enforce_strict_ordering] + + output.puts('') + output.puts('int GlobalExpectCount;') + output.puts('int GlobalVerifyOrder;') + output.puts('char* GlobalOrderError;') + end + + def create_externs(output, tests, _mocks) + output.puts("\n/*=======External Functions This Runner Calls=====*/") + output.puts("extern void #{@options[:setup_name]}(void);") + output.puts("extern void #{@options[:teardown_name]}(void);") + output.puts("\n#ifdef __cplusplus\nextern \"C\"\n{\n#endif") if @options[:externc] + tests.each do |test| + output.puts("extern void #{test[:test]}(#{test[:call] || 'void'});") + end + output.puts("#ifdef __cplusplus\n}\n#endif") if @options[:externc] + output.puts('') + end + + def create_mock_management(output, mock_headers) + output.puts("\n/*=======Mock Management=====*/") + output.puts('static void CMock_Init(void)') + output.puts('{') + + if @options[:enforce_strict_ordering] + output.puts(' GlobalExpectCount = 0;') + output.puts(' GlobalVerifyOrder = 0;') + output.puts(' GlobalOrderError = NULL;') + end + + mocks = mock_headers.map { |mock| File.basename(mock, '.*') } + mocks.each do |mock| + mock_clean = TypeSanitizer.sanitize_c_identifier(mock) + output.puts(" #{mock_clean}_Init();") + end + output.puts("}\n") + + output.puts('static void CMock_Verify(void)') + output.puts('{') + mocks.each do |mock| + mock_clean = TypeSanitizer.sanitize_c_identifier(mock) + output.puts(" #{mock_clean}_Verify();") + end + output.puts("}\n") + + output.puts('static void CMock_Destroy(void)') + output.puts('{') + mocks.each do |mock| + mock_clean = TypeSanitizer.sanitize_c_identifier(mock) + output.puts(" #{mock_clean}_Destroy();") + end + output.puts("}\n") + end + + def create_setup(output) + return if @options[:has_setup] + + output.puts("\n/*=======Setup (stub)=====*/") + output.puts("void #{@options[:setup_name]}(void) {}") + end + + def create_teardown(output) + return if @options[:has_teardown] + + output.puts("\n/*=======Teardown (stub)=====*/") + output.puts("void #{@options[:teardown_name]}(void) {}") + end + + def create_suite_setup(output) + return if @options[:suite_setup].nil? + + output.puts("\n/*=======Suite Setup=====*/") + output.puts('void suiteSetUp(void)') + output.puts('{') + output.puts(@options[:suite_setup]) + output.puts('}') + end + + def create_suite_teardown(output) + return if @options[:suite_teardown].nil? + + output.puts("\n/*=======Suite Teardown=====*/") + output.puts('int suiteTearDown(int num_failures)') + output.puts('{') + output.puts(@options[:suite_teardown]) + output.puts('}') + end + + def create_reset(output) + output.puts("\n/*=======Test Reset Options=====*/") + output.puts("void #{@options[:test_reset_name]}(void);") + output.puts("void #{@options[:test_reset_name]}(void)") + output.puts('{') + output.puts(" #{@options[:teardown_name]}();") + output.puts(' CMock_Verify();') + output.puts(' CMock_Destroy();') + output.puts(' CMock_Init();') + output.puts(" #{@options[:setup_name]}();") + output.puts('}') + output.puts("void #{@options[:test_verify_name]}(void);") + output.puts("void #{@options[:test_verify_name]}(void)") + output.puts('{') + output.puts(' CMock_Verify();') + output.puts('}') + end + + def create_run_test(output) + require 'erb' + template = ERB.new(File.read(File.join(__dir__, 'run_test.erb')), nil, '<>') + output.puts("\n" + template.result(binding)) + end + + def create_args_wrappers(output, tests) + return unless @options[:use_param_tests] + + output.puts("\n/*=======Parameterized Test Wrappers=====*/") + tests.each do |test| + next if test[:args].nil? || test[:args].empty? + + test[:args].each.with_index(1) do |args, idx| + output.puts("static void runner_args#{idx}_#{test[:test]}(void)") + output.puts('{') + output.puts(" #{test[:test]}(#{args});") + output.puts("}\n") + end + end + end + + def create_main(output, filename, tests, used_mocks) + output.puts("\n/*=======MAIN=====*/") + main_name = @options[:main_name].to_sym == :auto ? "main_#{filename.gsub('.c', '')}" : (@options[:main_name]).to_s + if @options[:cmdline_args] + if main_name != 'main' + output.puts("#{@options[:main_export_decl]} int #{main_name}(int argc, char** argv);") + end + output.puts("#{@options[:main_export_decl]} int #{main_name}(int argc, char** argv)") + output.puts('{') + output.puts(' int parse_status = UnityParseOptions(argc, argv);') + output.puts(' if (parse_status != 0)') + output.puts(' {') + output.puts(' if (parse_status < 0)') + output.puts(' {') + output.puts(" UnityPrint(\"#{filename.gsub('.c', '')}.\");") + output.puts(' UNITY_PRINT_EOL();') + tests.each do |test| + if (!@options[:use_param_tests]) || test[:args].nil? || test[:args].empty? + output.puts(" UnityPrint(\" #{test[:test]}\");") + output.puts(' UNITY_PRINT_EOL();') + else + test[:args].each do |args| + output.puts(" UnityPrint(\" #{test[:test]}(#{args})\");") + output.puts(' UNITY_PRINT_EOL();') + end + end + end + output.puts(' return 0;') + output.puts(' }') + output.puts(' return parse_status;') + output.puts(' }') + else + main_return = @options[:omit_begin_end] ? 'void' : 'int' + if main_name != 'main' + output.puts("#{@options[:main_export_decl]} #{main_return} #{main_name}(void);") + end + output.puts("#{main_return} #{main_name}(void)") + output.puts('{') + end + output.puts(' suiteSetUp();') if @options[:has_suite_setup] + if @options[:omit_begin_end] + output.puts(" UnitySetTestFile(\"#{filename.gsub(/\\/, '\\\\\\')}\");") + else + output.puts(" UnityBegin(\"#{filename.gsub(/\\/, '\\\\\\')}\");") + end + tests.each do |test| + if (!@options[:use_param_tests]) || test[:args].nil? || test[:args].empty? + output.puts(" run_test(#{test[:test]}, \"#{test[:test]}\", #{test[:line_number]});") + else + test[:args].each.with_index(1) do |args, idx| + wrapper = "runner_args#{idx}_#{test[:test]}" + testname = "#{test[:test]}(#{args})".dump + output.puts(" run_test(#{wrapper}, #{testname}, #{test[:line_number]});") + end + end + end + output.puts + output.puts(' CMock_Guts_MemFreeFinal();') unless used_mocks.empty? + if @options[:has_suite_teardown] + if @options[:omit_begin_end] + output.puts(' (void) suite_teardown(0);') + else + output.puts(' return suiteTearDown(UnityEnd());') + end + else + output.puts(' return UnityEnd();') unless @options[:omit_begin_end] + end + output.puts('}') + end + + def create_h_file(output, filename, tests, testfile_includes, used_mocks) + filename = File.basename(filename).gsub(/[-\/\\\.\,\s]/, '_').upcase + output.puts('/* AUTOGENERATED FILE. DO NOT EDIT. */') + output.puts("#ifndef _#{filename}") + output.puts("#define _#{filename}\n\n") + output.puts("#include \"#{@options[:framework]}.h\"") + output.puts('#include "cmock.h"') unless used_mocks.empty? + @options[:includes].flatten.uniq.compact.each do |inc| + output.puts("#include #{inc.include?('<') ? inc : "\"#{inc}\""}") + end + testfile_includes.each do |inc| + output.puts("#include #{inc.include?('<') ? inc : "\"#{inc}\""}") + end + output.puts "\n" + tests.each do |test| + if test[:params].nil? || test[:params].empty? + output.puts("void #{test[:test]}(void);") + else + output.puts("void #{test[:test]}(#{test[:params]});") + end + end + output.puts("#endif\n\n") + end +end + +if $0 == __FILE__ + options = { includes: [] } + + # parse out all the options first (these will all be removed as we go) + ARGV.reject! do |arg| + case arg + when '-cexception' + options[:plugins] = [:cexception] + true + when /\.*\.ya?ml$/ + options = UnityTestRunnerGenerator.grab_config(arg) + true + when /--(\w+)=\"?(.*)\"?/ + options[Regexp.last_match(1).to_sym] = Regexp.last_match(2) + true + when /\.*\.(?:hpp|hh|H|h)$/ + options[:includes] << arg + true + else false + end + end + + # make sure there is at least one parameter left (the input file) + unless ARGV[0] + puts ["\nusage: ruby #{__FILE__} (files) (options) input_test_file (output)", + "\n input_test_file - this is the C file you want to create a runner for", + ' output - this is the name of the runner file to generate', + ' defaults to (input_test_file)_Runner', + ' files:', + ' *.yml / *.yaml - loads configuration from here in :unity or :cmock', + ' *.h - header files are added as #includes in runner', + ' options:', + ' -cexception - include cexception support', + ' -externc - add extern "C" for cpp support', + ' --setup_name="" - redefine setUp func name to something else', + ' --teardown_name="" - redefine tearDown func name to something else', + ' --main_name="" - redefine main func name to something else', + ' --test_prefix="" - redefine test prefix from default test|spec|should', + ' --test_reset_name="" - redefine resetTest func name to something else', + ' --test_verify_name="" - redefine verifyTest func name to something else', + ' --suite_setup="" - code to execute for setup of entire suite', + ' --suite_teardown="" - code to execute for teardown of entire suite', + ' --use_param_tests=1 - enable parameterized tests (disabled by default)', + ' --omit_begin_end=1 - omit calls to UnityBegin and UnityEnd (disabled by default)', + ' --header_file="" - path/name of test header file to generate too'].join("\n") + exit 1 + end + + # create the default test runner name if not specified + ARGV[1] = ARGV[0].gsub('.c', '_Runner.c') unless ARGV[1] + + UnityTestRunnerGenerator.new(options).run(ARGV[0], ARGV[1]) +end diff --git a/test/tools/meson.build b/test/tools/meson.build new file mode 100644 index 0000000..7e9b122 --- /dev/null +++ b/test/tools/meson.build @@ -0,0 +1,6 @@ +generate_test_runner = find_program('generate_test_runner.rb') +test_runner_generator = generator( + generate_test_runner, + output: ['@BASENAME@_Runner.c'], + arguments: ['@INPUT@', '@OUTPUT@'] +) diff --git a/test/tools/run_test.erb b/test/tools/run_test.erb new file mode 100644 index 0000000..f91b566 --- /dev/null +++ b/test/tools/run_test.erb @@ -0,0 +1,37 @@ +/*=======Test Runner Used To Run Each Test=====*/ +static void run_test(UnityTestFunction func, const char* name, UNITY_LINE_TYPE line_num) +{ + Unity.CurrentTestName = name; + Unity.CurrentTestLineNumber = line_num; +#ifdef UNITY_USE_COMMAND_LINE_ARGS + if (!UnityTestMatches()) + return; +#endif + Unity.NumberOfTests++; + UNITY_CLR_DETAILS(); + UNITY_EXEC_TIME_START(); + CMock_Init(); + if (TEST_PROTECT()) + { +<% if @options[:plugins].include?(:cexception) %> + CEXCEPTION_T e; + Try { + <%= @options[:setup_name] %>(); + func(); + } Catch(e) { + TEST_ASSERT_EQUAL_HEX32_MESSAGE(CEXCEPTION_NONE, e, "Unhandled Exception!"); + } +<% else %> + <%= @options[:setup_name] %>(); + func(); +<% end %> + } + if (TEST_PROTECT()) + { + <%= @options[:teardown_name] %>(); + CMock_Verify(); + } + CMock_Destroy(); + UNITY_EXEC_TIME_STOP(); + UnityConcludeTest(); +} diff --git a/test/tools/type_sanitizer.rb b/test/tools/type_sanitizer.rb new file mode 100644 index 0000000..dafb882 --- /dev/null +++ b/test/tools/type_sanitizer.rb @@ -0,0 +1,6 @@ +module TypeSanitizer + def self.sanitize_c_identifier(unsanitized) + # convert filename to valid C identifier by replacing invalid chars with '_' + unsanitized.gsub(/[-\/\\\.\,\s]/, '_') + end +end -- GitLab From cea4951ac19c2fc80d334756930967b33bd795c0 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Thu, 17 Mar 2022 17:49:43 +0100 Subject: [PATCH 17/46] refactor & test Rice and Golomb Encoder --- test/meson.build | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/meson.build b/test/meson.build index c3a6afb..b843e00 100644 --- a/test/meson.build +++ b/test/meson.build @@ -30,12 +30,14 @@ if cppcheck.found() ) endif +subdir('tools') + subdir('cmp_tool') cunit_dep = dependency('cunit', required : false) -if cunit_dep.found() - subdir('cmp_icu') -else - message('C Unit Testing Framework not found! Skipping unit tests.') -endif +unity_dep = dependency('unity', fallback : ['unity', 'unity_dep']) +# unity_proj = subproject('unity') +# unity_dep = unity_proj.get_variable('unity_dep') + +subdir('cmp_icu') -- GitLab From b402a986eb161d17413a29e7d276a97da490ed02 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Tue, 22 Mar 2022 16:28:07 +0100 Subject: [PATCH 18/46] refactor and test for encode_value_zero --- lib/cmp_icu_new.c | 299 +++++++++++++++++++- test/cmp_icu/test_cmp_icu_new.c | 469 ++++++++++++++++++++++++++++++-- 2 files changed, 735 insertions(+), 33 deletions(-) diff --git a/lib/cmp_icu_new.c b/lib/cmp_icu_new.c index e6f6752..f4bec36 100644 --- a/lib/cmp_icu_new.c +++ b/lib/cmp_icu_new.c @@ -26,6 +26,52 @@ /* return code if the bitstream buffer is too small to store the whole bitstream */ #define CMP_ERROR_SAMLL_BUF -2 +/* pointer to a code word generation function */ +typedef uint32_t (*generate_cw_pt)(uint32_t value, uint32_t encoder_par1, + uint32_t encoder_par2, uint32_t *cw); + +/* typedef uint32_t (*next_model_f_pt)(uint32_t model_mode_val, uint32_t diff_model_var); */ + +struct encoder_setupt { + /* unsigned int lossy_par; */ + /* next_model_f_pt *next_model_f; */ + generate_cw_pt generate_cw; /* pointer to the code word generation function */ + /* uint32_t updated_model; */ + uint32_t encoder_par1; + uint32_t encoder_par2; + uint32_t outlier_par; + uint32_t model_value; + uint32_t max_value_bits; /* how many bits are needed to represent the highest possible value */ + uint32_t max_bit_len; /* maximum length of the bitstream/icu_output_buf in bits */ + uint32_t *bitstream_adr; +}; + + +/** + * @brief map a signed value into a positive value range + * + * @param value_to_map signed value to map + * @param max_value_bits how many bits are needed to represent the + * highest possible value + * + * @returns the positive mapped value + */ + +static uint32_t map_to_pos(uint32_t value_to_map, unsigned int max_value_bits) +{ + uint32_t mask = (~0U >> (32 - max_value_bits)); /* mask the used bits */ + + value_to_map &= mask; + if (value_to_map >> (max_value_bits - 1)) { /* check the leading signed bit */ + value_to_map |= ~mask; /* convert to 32-bit signed integer */ + /* map negative values to uneven numbers */ + return (-value_to_map) * 2 - 1; /* possible integer overflow is intended */ + } else { + /* map positive values to even numbers */ + return value_to_map * 2; /* possible integer overflow is intended */ + } +} + /** * @brief put the value of up to 32 bits into a bitstream accessed as 32-bit @@ -43,8 +89,6 @@ * @returns length in bits of the generated bitstream on success; returns * negative in case of erroneous input; returns CMP_ERROR_SAMLL_BUF if * the bitstream buffer is too small to put the value in the bitstream - * @note a value with more bits set as the n_bits parameter is considered as an - * erroneous input. */ static int put_n_bits32(uint32_t value, unsigned int n_bits, int bit_offset, @@ -60,21 +104,11 @@ static int put_n_bits32(uint32_t value, unsigned int n_bits, int bit_offset, return -1; if (n_bits == 0) - return 0; + return stream_len; if (n_bits > 32) return -1; - /* (M) is the n_bits parameter large enough to cover all value bits; the - * calculations can be re-used in the unsegmented code, so we have no overhead - */ - shiftRight = 32 - n_bits; - mask = 0xFFFFFFFFU >> shiftRight; - if (value & ~mask) { - debug_print("Error: Not all set bits in the put value are added to the bitstream. Check value n_bits parameter combination.\n"); - return -1; - } - /* Do we need to write data to the bitstream? */ if (!bitstream_adr) return stream_len; @@ -85,6 +119,13 @@ static int put_n_bits32(uint32_t value, unsigned int n_bits, int bit_offset, return CMP_ERROR_SAMLL_BUF; } + /* (M) is the n_bits parameter large enough to cover all value bits; the + * calculations can be re-used in the unsegmented code, so we have no overhead + */ + shiftRight = 32 - n_bits; + mask = 0xFFFFFFFFU >> shiftRight; + value &= mask; + /* Separate the bit_offset into word offset (set local_adr pointer) and local bit offset (bitsLeft) */ local_adr = bitstream_adr + (bit_offset >> 5); bitsLeft = bit_offset & 0x1F; @@ -163,3 +204,235 @@ static int put_n_bits32(uint32_t value, unsigned int n_bits, int bit_offset, return stream_len; } + +/** + * @brief forms the codeword according to the Rice code + * + * @param value value to be encoded + * @param m Golomb parameter, only m's which are power of 2 are allowed + * @param log2_m Rice parameter, is log_2(m) calculate outside function + * for better performance + * @param cw address were the encode code word is stored + * + * @returns the length of the formed code word in bits + * @note no check if the generated code word is not longer than 32 bits. + */ + +static uint32_t Rice_encoder(uint32_t value, uint32_t m, uint32_t log2_m, + uint32_t *cw) +{ + uint32_t g; /* quotient of value/m */ + uint32_t q; /* quotient code without ending zero */ + uint32_t r; /* remainder of value/m */ + uint32_t rl; /* remainder length */ + + g = value >> log2_m; /* quotient, number of leading bits */ + q = (1U << g) - 1; /* prepare the quotient code without ending zero */ + + r = value & (m-1); /* calculate the remainder */ + rl = log2_m + 1; /* length of the remainder (+1 for the 0 in the quotient code) */ + *cw = (q << rl) | r; /* put the quotient and remainder code together */ + /* + * NOTE: If log2_m = 31 -> rl = 32, (q << rl) leads to an undefined + * behavior. However, in this case, a valid code with a maximum of 32 + * bits can only be formed if q = 0. Any shift with 0 << x always + * results in 0, which forms the correct codeword in this case. For + * performance reasons, this undefined behaviour is not caught. + */ + + return rl + g; /* calculate the length of the code word */ +} + + +/** + * @brief forms a codeword according to the Golomb code + * + * @param value value to be encoded + * @param m Golomb parameter (have to be bigger than 0) + * @param log2_m is log_2(m) calculate outside function for better + * performance + * @param cw address were the formed code word is stored + * + * @returns the length of the formed code word in bits + */ + +static uint32_t Golomb_encoder(uint32_t value, uint32_t m, uint32_t log2_m, + uint32_t *cw) +{ + uint32_t len0, b, g, q, lg; + uint32_t len; + uint32_t cutoff; + + len0 = log2_m + 1; /* codeword length in group 0 */ + cutoff = (1U << (log2_m + 1)) - m; /* members in group 0 */ + + if (value < cutoff) { /* group 0 */ + *cw = value; + len = len0; + } else { /* other groups */ + g = (value-cutoff) / m; /* this group is which one */ + b = cutoff << 1; /* form the base codeword */ + lg = len0 + g; /* it has lg remainder bits */ + q = (1U << g) - 1; /* prepare the left side in unary */ + *cw = (q << (len0+1)) + b + (value-cutoff) - g*m; /* composed codeword */ + len = lg + 1; /* length of the codeword */ + } + return len; +} + + +/** + * @brief generate a code word without a outlier mechanism and put in the + * bitstream + * + * @param value value to encode in the bitstream + * @param stream_len length of the bitstream in bits + * @param setup pointer to the encoder setup + * + * @returns the bit length of the bitstream with the added encoded value on + * success; negative on error + */ + +static int encode_normal(uint32_t value, int stream_len, + struct encoder_setupt *setup) +{ + uint32_t code_word, cw_len; + + cw_len = setup->generate_cw(value, setup->encoder_par1, + setup->encoder_par2, &code_word); + + return put_n_bits32(code_word, cw_len, stream_len, setup->bitstream_adr, + setup->max_bit_len); +} + + +/** + * @brief subtract the model from the data and encode the result and put it into + * bitstream, for outlier use the zero escape symbol mechanism + * + * @param data data to encode + * @param model model of the data (0 if not used) + * @param stream_len length of the bitstream in bits + * @param setup pointer to the encoder setup + * + * @returns the bit length of the bitstream with the added encoded value on + * success; negative on error + * + * @note no check if data or model are in the allowed range + */ + +static int encode_value_zero(uint32_t data, uint32_t model, int stream_len, + struct encoder_setupt *setup) +{ + data -= model; + + data = map_to_pos(data, setup->max_value_bits); + + /* For performance reasons we check if there is an outlier before adding + * one to the data, than the other way around: + * data++; + * if (data < setup->outlier_par && data != 0) + * return ... + */ + if (data < (setup->outlier_par - 1)) { + data++; /* add 1 to every value so we can use 0 as escape symbol */ + return encode_normal(data, stream_len, setup); + } + + data++; /* add 1 to every value so we can use 0 as escape symbol */ + + /* use zero as escape symbol */ + stream_len = encode_normal(0, stream_len, setup); + if (stream_len <= 0) + return stream_len; + + /* put the data unencoded in the bitstream */ + stream_len = put_n_bits32(data, setup->max_value_bits, stream_len, + setup->bitstream_adr, setup->max_bit_len); + + return stream_len; + +} + + +static int cal_multi_offset(unsigned int unencoded_data) +{ + if (unencoded_data <= 0x3) + return 0; + if (unencoded_data <= 0xF) + return 1; + if (unencoded_data <= 0x3F) + return 2; + if (unencoded_data <= 0xFF) + return 3; + if (unencoded_data <= 0x3FF) + return 4; + if (unencoded_data <= 0xFFF) + return 5; + if (unencoded_data <= 0x3FFF) + return 6; + if (unencoded_data <= 0xFFFF) + return 7; + if (unencoded_data <= 0x3FFFF) + return 8; + if (unencoded_data <= 0xFFFFF) + return 9; + if (unencoded_data <= 0x3FFFFF) + return 10; + if (unencoded_data <= 0xFFFFFF) + return 11; + if (unencoded_data <= 0x3FFFFFF) + return 12; + if (unencoded_data <= 0xFFFFFFF) + return 13; + if (unencoded_data <= 0x3FFFFFFF) + return 14; + else + return 15; +} + + +#if 0 +static int encode_value_multi(uint32_t data, uint32_t model, int stream_len, + struct encoder_setupt *setup) +{ + uint32_t unencoded_data; + unsigned int unencoded_data_len; + uint32_t escape_sym; + uint32_t escape_sym_offset; + + data -= model; /* possible underflow is intended */ + + data = map_to_pos(data, setup->max_value_bits); + + if (data < setup->outlier_par) + return encode_normal(data, stream_len, setup); + /* + * In this mode we put the difference between the data and the spillover + * threshold value (unencoded_data) after a encoded escape symbol, which + * indicate that the next codeword is unencoded. + * We use different escape symbol depended on the size the needed bit of + * unencoded data: + * 0, 1, 2 bits needed for unencoded data -> escape symbol is spill + 0 + * 3, 4 bits needed for unencoded data -> escape symbol is spill + 1 + * .. + */ + + unencoded_data = data - setup->outlier_par; + + escape_sym_offset = cal_multi_offset(unencoded_data); + escape_sym = setup->outlier_par + escape_sym_offset; + unencoded_data_len = (escape_sym_offset + 1) * 2; + + /* put the escape symbol in the bitstream */ + stream_len = encode_normal(escape_sym, stream_len, setup); + if (stream_len <= 0) + return stream_len; + + /* put the unencoded data in the bitstream */ + stream_len = put_n_bits32(unencoded_data, unencoded_data_len, stream_len, + setup->bitstream_adr, setup->max_bit_len); + + return stream_len; +} +#endif diff --git a/test/cmp_icu/test_cmp_icu_new.c b/test/cmp_icu/test_cmp_icu_new.c index 6e57615..986b7b6 100644 --- a/test/cmp_icu/test_cmp_icu_new.c +++ b/test/cmp_icu/test_cmp_icu_new.c @@ -2,10 +2,99 @@ #include "unity.h" +#include "cmp_support.h" /* this is a hack to test static functions */ #include "../lib/cmp_icu_new.c" +/** +* @test map_to_pos +*/ + +void test_map_to_pos(void) +{ + uint32_t value_to_map; + uint32_t max_value_bits; + uint32_t mapped_value; + + /* test mapping 32 bits values */ + max_value_bits = 32; + + value_to_map = 0; + mapped_value = map_to_pos(value_to_map, max_value_bits); + TEST_ASSERT_EQUAL_INT(0, mapped_value); + + value_to_map = -1U; + mapped_value = map_to_pos(value_to_map, max_value_bits); + TEST_ASSERT_EQUAL_INT(1, mapped_value); + + value_to_map = 1; + mapped_value = map_to_pos(value_to_map, max_value_bits); + TEST_ASSERT_EQUAL_INT(2, mapped_value); + + value_to_map = 42; + mapped_value = map_to_pos(value_to_map, max_value_bits); + TEST_ASSERT_EQUAL_INT(84, mapped_value); + + value_to_map = INT32_MAX; + mapped_value = map_to_pos(value_to_map, max_value_bits); + TEST_ASSERT_EQUAL_HEX(UINT32_MAX-1, mapped_value); + + value_to_map = INT32_MIN; + mapped_value = map_to_pos(value_to_map, max_value_bits); + TEST_ASSERT_EQUAL_HEX(UINT32_MAX, mapped_value); + + /* test mapping 16 bits values */ + max_value_bits = 16; + + value_to_map = -1U; + mapped_value = map_to_pos(value_to_map, max_value_bits); + TEST_ASSERT_EQUAL_INT(1, mapped_value); + + /* test mapping 6 bits values */ + max_value_bits = 6; + + value_to_map = 0; + mapped_value = map_to_pos(value_to_map, max_value_bits); + TEST_ASSERT_EQUAL_INT(0, mapped_value); + + value_to_map = -1U; + mapped_value = map_to_pos(value_to_map, max_value_bits); + TEST_ASSERT_EQUAL_INT(1, mapped_value); + + value_to_map = UINT32_MAX; + mapped_value = map_to_pos(value_to_map, max_value_bits); + TEST_ASSERT_EQUAL_INT(1, mapped_value); + + value_to_map = -1U & 0x3FU; + mapped_value = map_to_pos(value_to_map, max_value_bits); + TEST_ASSERT_EQUAL_INT(1, mapped_value); + + value_to_map = 63; + mapped_value = map_to_pos(value_to_map, max_value_bits); + TEST_ASSERT_EQUAL_INT(1, mapped_value); + + value_to_map = 1; + mapped_value = map_to_pos(value_to_map, max_value_bits); + TEST_ASSERT_EQUAL_INT(2, mapped_value); + + value_to_map = 31; + mapped_value = map_to_pos(value_to_map, max_value_bits); + TEST_ASSERT_EQUAL_INT(62, mapped_value); + + value_to_map = -33U; /* aka 31 */ + mapped_value = map_to_pos(value_to_map, max_value_bits); + TEST_ASSERT_EQUAL_INT(62, mapped_value); + + value_to_map = -32U; + mapped_value = map_to_pos(value_to_map, max_value_bits); + TEST_ASSERT_EQUAL_INT(63, mapped_value); + + value_to_map = 32; + mapped_value = map_to_pos(value_to_map, max_value_bits); + TEST_ASSERT_EQUAL_INT(63, mapped_value); +} + /** * @test put_n_bits32 @@ -42,7 +131,11 @@ void test_put_n_bits32(void) init_PB32_arrays(testarray0, testarray1); TEST_ASSERT(testarray0[0] == 0); + TEST_ASSERT(testarray0[1] == 0); + TEST_ASSERT(testarray0[2] == 0); TEST_ASSERT(testarray1[0] == 0xffffffff); + TEST_ASSERT(testarray1[1] == 0xffffffff); + TEST_ASSERT(testarray1[2] == 0xffffffff); /*** n=0 ***/ @@ -56,7 +149,9 @@ void test_put_n_bits32(void) TEST_ASSERT_EQUAL_INT(0, rval); TEST_ASSERT(testarray1[0] == 0xffffffff); - /* TODO: not a valid test */ + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(0, rval); + v = 0xffffffff; n = 0; o = 0; rval = put_n_bits32(v, n, o, testarray0, l); TEST_ASSERT_EQUAL_INT(0, rval); @@ -66,27 +161,35 @@ void test_put_n_bits32(void) TEST_ASSERT_EQUAL_INT(0, rval); TEST_ASSERT(testarray1[0] == 0xffffffff); + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(0, rval); + /* do not write, right border */ - v = 0; n = 0; o = 31; + v = 0; n = 0; o = l; rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(0, rval); + TEST_ASSERT_EQUAL_INT(l, rval); TEST_ASSERT(testarray0[0] == 0); rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT_EQUAL_INT(0, rval); + TEST_ASSERT_EQUAL_INT(l, rval); TEST_ASSERT(testarray1[0] == 0xffffffff); - /* TODO: not a valid test */ + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(l, rval); + /* test value = 0xffffffff; N = 0 */ - v = 0xffffffff; n = 0; o = 31; + v = 0xffffffff; n = 0; o = l; rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(0, rval); + TEST_ASSERT_EQUAL_INT(l, rval); TEST_ASSERT(testarray0[0] == 0); rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT_EQUAL_INT(0, rval); + TEST_ASSERT_EQUAL_INT(l, rval); TEST_ASSERT(testarray1[0] == 0xffffffff); + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(l, rval); + /*** n=1 ***/ /* left border, write 0 */ @@ -114,10 +217,12 @@ void test_put_n_bits32(void) rval = put_n_bits32(v, n, o, testarray0, l); TEST_ASSERT_EQUAL_INT(rval, 32); TEST_ASSERT(testarray0[0] == 0xf0f0abcd); + TEST_ASSERT(testarray0[1] == 0); rval = put_n_bits32(v, n, o, testarray1, l); TEST_ASSERT_EQUAL_INT(rval, 32); TEST_ASSERT(testarray1[0] == 0xf0f0abcd); + TEST_ASSERT(testarray1[1] == 0xffffffff); /* re-init input arrays after clobbering */ init_PB32_arrays(testarray0, testarray1); @@ -317,26 +422,49 @@ void test_put_n_bits32(void) rval = put_n_bits32(v, n, o, NULL, l); TEST_ASSERT_EQUAL_INT(rval, 97); /* rval can be longer than l */ - /* error cases */ - /* n too large */ - v = 0x0; n = 33; o = 1; + /* value larger than n allows */ + v = 0x7f; n = 6; o = 10; rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(rval, -1); - TEST_ASSERT(testarray0[0] == 0); + TEST_ASSERT_EQUAL_INT(16, rval); + TEST_ASSERT(testarray0[0] == 0x003f0000); + TEST_ASSERT(testarray0[1] == 0); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(16, rval); + TEST_ASSERT(testarray1[0] == 0xffffffff); + TEST_ASSERT(testarray1[1] == 0xffffffff); + + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(16, rval); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + v = 0xffffffff; n = 6; o = 10; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(16, rval); + TEST_ASSERT(testarray0[0] == 0x003f0000); TEST_ASSERT(testarray0[1] == 0); + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(16, rval); + TEST_ASSERT(testarray1[0] == 0xffffffff); + TEST_ASSERT(testarray1[1] == 0xffffffff); + rval = put_n_bits32(v, n, o, NULL, l); - TEST_ASSERT_EQUAL_INT(rval, -1); + TEST_ASSERT_EQUAL_INT(16, rval); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); - /* value larger than n allows */ - v = 0x7f; n = 6; o = 10; + /*** error cases ***/ + /* n too large */ + v = 0x0; n = 33; o = 1; rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(-1, rval); + TEST_ASSERT_EQUAL_INT(rval, -1); TEST_ASSERT(testarray0[0] == 0); TEST_ASSERT(testarray0[1] == 0); rval = put_n_bits32(v, n, o, NULL, l); - TEST_ASSERT_EQUAL_INT(-1, rval); + TEST_ASSERT_EQUAL_INT(rval, -1); /* try to put too much in the bitstream */ v = 0x1; n = 1; o = 96; @@ -346,11 +474,11 @@ void test_put_n_bits32(void) TEST_ASSERT(testarray0[1] == 0); TEST_ASSERT(testarray0[2] == 0); - /* this should work */ + /* this should work (if bitstream=NULL no length check) */ rval = put_n_bits32(v, n, o, NULL, l); TEST_ASSERT_EQUAL_INT(97, rval); - /* offset lager than max_bit_len */ + /* offset lager than max_bit_len(l) */ v = 0x0; n = 32; o = INT32_MAX; rval = put_n_bits32(v, n, o, testarray1, l); TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, rval); @@ -370,4 +498,305 @@ void test_put_n_bits32(void) rval = put_n_bits32(v, n, o, NULL, l); TEST_ASSERT_EQUAL_INT(-1, rval); + + v = 0x0; n = 0; o = -2; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(-1, rval); + TEST_ASSERT(testarray0[0] == 0); + TEST_ASSERT(testarray0[1] == 0); + + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(-1, rval); +} + + +/** + * @test Rice_encoder + */ + +void test_Rice_encoder(void) +{ + uint32_t value, g_par, log2_g_par, cw, cw_len; + + /* test minimum Golomb parameter */ + value = 0; log2_g_par = (uint32_t)ilog_2(MIN_ICU_GOLOMB_PAR); g_par = 1U << log2_g_par; cw = ~0U; + cw_len = Rice_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(1, cw_len); + TEST_ASSERT_EQUAL_HEX(0x0, cw); + + value = 31; + cw_len = Rice_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFE, cw); + + /* test some arbitrary values */ + value = 0; log2_g_par = 4; g_par = 1U << log2_g_par; cw = ~0U; + cw_len = Rice_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(5, cw_len); + TEST_ASSERT_EQUAL_HEX(0x0, cw); + + value = 1; + cw_len = Rice_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(5, cw_len); + TEST_ASSERT_EQUAL_HEX(0x1, cw); + + value = 42; + cw_len = Rice_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(7, cw_len); + TEST_ASSERT_EQUAL_HEX(0x6a, cw); + + value = 446; + cw_len = Rice_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFEE, cw); + + value = 447; + cw_len = Rice_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFEF, cw); + + /* test maximum Golomb parameter for Rice_encoder */ + value = 0; log2_g_par = (uint32_t)ilog_2(MAX_ICU_GOLOMB_PAR); g_par = 1U << log2_g_par; cw = ~0U; + cw_len = Rice_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0x0, cw); + + value = 1; + cw_len = Rice_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0x1, cw); + + value = 0x7FFFFFFE; + cw_len = Rice_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0x7FFFFFFE, cw); + + value = 0x7FFFFFFF; + cw_len = Rice_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0x7FFFFFFF, cw); +} + + +/** + * @test Golomb_encoder + */ + +void test_Golomb_encoder(void) +{ + uint32_t value, g_par, log2_g_par, cw, cw_len; + + /* test minimum Golomb parameter */ + value = 0; g_par = MIN_ICU_GOLOMB_PAR; log2_g_par = (uint32_t)ilog_2(g_par); cw = ~0U; + cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(1, cw_len); + TEST_ASSERT_EQUAL_HEX(0x0, cw); + + value = 31; + cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFE, cw); + + + /* test some arbitrary values with g_par = 16 */ + value = 0; g_par = 16; log2_g_par = (uint32_t)ilog_2(g_par); cw = ~0U; + cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(5, cw_len); + TEST_ASSERT_EQUAL_HEX(0x0, cw); + + value = 1; + cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(5, cw_len); + TEST_ASSERT_EQUAL_HEX(0x1, cw); + + value = 42; + cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(7, cw_len); + TEST_ASSERT_EQUAL_HEX(0x6a, cw); + + value = 446; + cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFEE, cw); + + value = 447; + cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFEF, cw); + + + /* test some arbitrary values with g_par = 3 */ + value = 0; g_par = 3; log2_g_par = (uint32_t)ilog_2(g_par); cw = ~0U; + cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(2, cw_len); + TEST_ASSERT_EQUAL_HEX(0x0, cw); + + value = 1; + cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(3, cw_len); + TEST_ASSERT_EQUAL_HEX(0x2, cw); + + value = 42; + cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(16, cw_len); + TEST_ASSERT_EQUAL_HEX(0xFFFC, cw); + + value = 44; + cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(17, cw_len); + TEST_ASSERT_EQUAL_HEX(0x1FFFB, cw); + + value = 88; + cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFA, cw); + + value = 89; + cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFB, cw); + + + /* test maximum Golomb parameter for Golomb_encoder */ + value = 0; g_par = MAX_ICU_GOLOMB_PAR; log2_g_par = (uint32_t)ilog_2(g_par); cw = ~0U; + cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0x0, cw); + + value = 1; g_par = MAX_ICU_GOLOMB_PAR; log2_g_par = (uint32_t)ilog_2(g_par); cw = ~0U; + cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0x1, cw); + + value = 0x7FFFFFFE; + cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0x7FFFFFFE, cw); + + value = 0x7FFFFFFF; + cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0x7FFFFFFF, cw); +} + + +/** + * @test encode_value_zero + */ + +void test_encode_value_zero(void) +{ + uint32_t data, model; + int stream_len; + struct encoder_setupt setup = {0}; + uint32_t bitstream[3] = {0}; + + /* setup the setup */ + setup.encoder_par1 = 1; + setup.encoder_par2 = (uint32_t)ilog_2(setup.encoder_par1); + setup.outlier_par = 32; + setup.max_value_bits = 32; + setup.generate_cw = Rice_encoder; + setup.bitstream_adr = bitstream; + setup.max_bit_len = sizeof(bitstream) * CHAR_BIT; + + stream_len = 0; + + data = 0; model = 0; + stream_len = encode_value_zero(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(2, stream_len); + TEST_ASSERT_EQUAL_HEX(0x80000000, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); + + data = 5; model = 0; + stream_len = encode_value_zero(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(14, stream_len); + TEST_ASSERT_EQUAL_HEX(0xBFF80000, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); + + data = 2; model = 7; + stream_len = encode_value_zero(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(25, stream_len); + TEST_ASSERT_EQUAL_HEX(0xBFFBFF00, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); + + /* zero escape mechanism */ + data = 100; model = 42; + /* (100-42)*2+1=117 -> cw 0 + 0x0000_0000_0000_0075 */ + stream_len = encode_value_zero(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(58, stream_len); + TEST_ASSERT_EQUAL_HEX(0xBFFBFF00, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0x00001D40, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); + + /* test overflow */ + data = INT32_MIN; model = 0; + /* (INT32_MIN)*-2-1+1=0(overflow) -> cw 0 + 0x0000_0000_0000_0000 */ + stream_len = encode_value_zero(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(91, stream_len); + TEST_ASSERT_EQUAL_HEX(0xBFFBFF00, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0x00001D40, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); + + /* small buffer error */ + data = 23; model = 26; + stream_len = encode_value_zero(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, stream_len); + + /* reset bitstream to all bits set */ + bitstream[0] = ~0U; + bitstream[1] = ~0U; + bitstream[2] = ~0U; + stream_len = 0; + + /* we use now values with maximum 6 bits */ + setup.max_value_bits = 6; + + /* lowest value before zero encoding */ + data = 53; model = 38; + stream_len = encode_value_zero(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(32, stream_len); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFE, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFF, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFF, bitstream[2]); + + /* lowest value with zero encoding */ + data = 0; model = 16; + stream_len = encode_value_zero(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(39, stream_len); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFE, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0x41FFFFFF, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFF, bitstream[2]); + + /* maximum positive value to encode */ + data = 31; model = 0; + stream_len = encode_value_zero(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(46, stream_len); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFE, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0x40FFFFFF, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFF, bitstream[2]); + + /* maximum negative value to encode */ + data = -32U; model = 0; + stream_len = encode_value_zero(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(53, stream_len); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFE, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0x40FC07FF, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFF, bitstream[2]); + + /* small buffer error when creating the zero escape symbol*/ + bitstream[0] = 0; + bitstream[1] = 0; + bitstream[2] = 0; + stream_len = 32; + setup.max_bit_len = 32; + data = 31; model = 0; + stream_len = encode_value_zero(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(-2, stream_len); + TEST_ASSERT_EQUAL_HEX(0, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0, bitstream[2]); } -- GitLab From 83ba84a190e41c8535a99b2cb930d43470615867 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Wed, 23 Mar 2022 14:43:20 +0100 Subject: [PATCH 19/46] refactor and test encode_value_multi() --- lib/cmp_icu_new.c | 92 ++++++++++++----------------- test/cmp_icu/test_cmp_icu_new.c | 100 ++++++++++++++++++++++++++++++-- 2 files changed, 134 insertions(+), 58 deletions(-) diff --git a/lib/cmp_icu_new.c b/lib/cmp_icu_new.c index f4bec36..d69ab0f 100644 --- a/lib/cmp_icu_new.c +++ b/lib/cmp_icu_new.c @@ -307,8 +307,8 @@ static int encode_normal(uint32_t value, int stream_len, /** - * @brief subtract the model from the data and encode the result and put it into - * bitstream, for outlier use the zero escape symbol mechanism + * @brief subtract the model from the data, encode the result and put it into + * bitstream, for encodeing outlier use the zero escape symbol mechanism * * @param data data to encode * @param model model of the data (0 if not used) @@ -316,9 +316,11 @@ static int encode_normal(uint32_t value, int stream_len, * @param setup pointer to the encoder setup * * @returns the bit length of the bitstream with the added encoded value on - * success; negative on error + * success; negative on error, CMP_ERROR_SAMLL_BUF if the bitstream buffer + * is too small to put the value in the bitstream * * @note no check if data or model are in the allowed range + * @note no check if the setup->outlier_par is in the allowed range */ static int encode_value_zero(uint32_t data, uint32_t model, int stream_len, @@ -328,13 +330,13 @@ static int encode_value_zero(uint32_t data, uint32_t model, int stream_len, data = map_to_pos(data, setup->max_value_bits); - /* For performance reasons we check if there is an outlier before adding - * one to the data, than the other way around: + /* For performance reasons, we check to see if there is an outlier + * before adding one, rather than the other way around: * data++; * if (data < setup->outlier_par && data != 0) * return ... */ - if (data < (setup->outlier_par - 1)) { + if (data < (setup->outlier_par - 1)) { /* detect r */ data++; /* add 1 to every value so we can use 0 as escape symbol */ return encode_normal(data, stream_len, setup); } @@ -355,74 +357,57 @@ static int encode_value_zero(uint32_t data, uint32_t model, int stream_len, } -static int cal_multi_offset(unsigned int unencoded_data) -{ - if (unencoded_data <= 0x3) - return 0; - if (unencoded_data <= 0xF) - return 1; - if (unencoded_data <= 0x3F) - return 2; - if (unencoded_data <= 0xFF) - return 3; - if (unencoded_data <= 0x3FF) - return 4; - if (unencoded_data <= 0xFFF) - return 5; - if (unencoded_data <= 0x3FFF) - return 6; - if (unencoded_data <= 0xFFFF) - return 7; - if (unencoded_data <= 0x3FFFF) - return 8; - if (unencoded_data <= 0xFFFFF) - return 9; - if (unencoded_data <= 0x3FFFFF) - return 10; - if (unencoded_data <= 0xFFFFFF) - return 11; - if (unencoded_data <= 0x3FFFFFF) - return 12; - if (unencoded_data <= 0xFFFFFFF) - return 13; - if (unencoded_data <= 0x3FFFFFFF) - return 14; - else - return 15; -} - +/** + * @brief subtract the model from the data, encode the result and put it into + * bitstream, for encoding outlier use the multi escape symbol mechanism + * + * @param data data to encode + * @param model model of the data (0 if not used) + * @param stream_len length of the bitstream in bits + * @param setup pointer to the encoder setup + * + * @returns the bit length of the bitstream with the added encoded value on + * success; negative on error, CMP_ERROR_SAMLL_BUF if the bitstream buffer + * is too small to put the value in the bitstream + * + * @note no check if data or model are in the allowed range + * @note no check if the setup->outlier_par is in the allowed ragne + */ -#if 0 static int encode_value_multi(uint32_t data, uint32_t model, int stream_len, struct encoder_setupt *setup) { uint32_t unencoded_data; unsigned int unencoded_data_len; - uint32_t escape_sym; - uint32_t escape_sym_offset; + uint32_t escape_sym, escape_sym_offset; data -= model; /* possible underflow is intended */ data = map_to_pos(data, setup->max_value_bits); - if (data < setup->outlier_par) + if (data < setup->outlier_par) /* detect non-outlier */ return encode_normal(data, stream_len, setup); + /* * In this mode we put the difference between the data and the spillover * threshold value (unencoded_data) after a encoded escape symbol, which * indicate that the next codeword is unencoded. * We use different escape symbol depended on the size the needed bit of * unencoded data: - * 0, 1, 2 bits needed for unencoded data -> escape symbol is spill + 0 - * 3, 4 bits needed for unencoded data -> escape symbol is spill + 1 - * .. + * 0, 1, 2 bits needed for unencoded data -> escape symbol is outlier_par + 0 + * 3, 4 bits needed for unencoded data -> escape symbol is outlier_par + 1 + * 5, 6 bits needed for unencoded data -> escape symbol is outlier_par + 2 + * and so on */ - unencoded_data = data - setup->outlier_par; - escape_sym_offset = cal_multi_offset(unencoded_data); - escape_sym = setup->outlier_par + escape_sym_offset; - unencoded_data_len = (escape_sym_offset + 1) * 2; + if (!unencoded_data) /* catch __builtin_clz(0) because the result is undefined.*/ + escape_sym_offset = 0; + else + escape_sym_offset = (31U - (uint32_t)__builtin_clz(unencoded_data)) >> 1; + + escape_sym = setup->outlier_par + escape_sym_offset; + unencoded_data_len = (escape_sym_offset + 1U) << 1; /* put the escape symbol in the bitstream */ stream_len = encode_normal(escape_sym, stream_len, setup); @@ -435,4 +420,3 @@ static int encode_value_multi(uint32_t data, uint32_t model, int stream_len, return stream_len; } -#endif diff --git a/test/cmp_icu/test_cmp_icu_new.c b/test/cmp_icu/test_cmp_icu_new.c index 986b7b6..a0d9109 100644 --- a/test/cmp_icu/test_cmp_icu_new.c +++ b/test/cmp_icu/test_cmp_icu_new.c @@ -8,8 +8,8 @@ /** -* @test map_to_pos -*/ + * @test map_to_pos + */ void test_map_to_pos(void) { @@ -780,7 +780,7 @@ void test_encode_value_zero(void) TEST_ASSERT_EQUAL_HEX(0xFFFFFFFF, bitstream[2]); /* maximum negative value to encode */ - data = -32U; model = 0; + data = 0; model = 32; stream_len = encode_value_zero(data, model, stream_len, &setup); TEST_ASSERT_EQUAL_INT(53, stream_len); TEST_ASSERT_EQUAL_HEX(0xFFFFFFFE, bitstream[0]); @@ -795,8 +795,100 @@ void test_encode_value_zero(void) setup.max_bit_len = 32; data = 31; model = 0; stream_len = encode_value_zero(data, model, stream_len, &setup); - TEST_ASSERT_EQUAL_INT(-2, stream_len); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, stream_len); TEST_ASSERT_EQUAL_HEX(0, bitstream[0]); TEST_ASSERT_EQUAL_HEX(0, bitstream[1]); TEST_ASSERT_EQUAL_HEX(0, bitstream[2]); } + + +/** + * @test encode_value_multi + */ + +void test_encode_value_multi(void) +{ + uint32_t data, model; + int stream_len; + struct encoder_setupt setup = {0}; + uint32_t bitstream[4] = {0}; + + /* setup the setup */ + setup.encoder_par1 = 1; + setup.encoder_par2 = (uint32_t)ilog_2(setup.encoder_par1); + setup.outlier_par = 16; + setup.max_value_bits = 32; + setup.generate_cw = Rice_encoder; + setup.bitstream_adr = bitstream; + setup.max_bit_len = sizeof(bitstream) * CHAR_BIT; + + stream_len = 0; + + data = 0; model = 0; + stream_len = encode_value_multi(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(1, stream_len); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[3]); + + data = 0; model = 1; + stream_len = encode_value_multi(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(3, stream_len); + TEST_ASSERT_EQUAL_HEX(0x40000000, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[3]); + + data = 1+23; model = 0+23; + stream_len = encode_value_multi(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(6, stream_len); + TEST_ASSERT_EQUAL_HEX(0x58000000, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[3]); + + /* highest value without multi outlier encoding */ + data = 0+42; model = 8+42; + stream_len = encode_value_multi(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(22, stream_len); + TEST_ASSERT_EQUAL_HEX(0x5BFFF800, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[3]); + + /* lowest value with multi outlier encoding */ + data = 8+42; model = 0+42; + stream_len = encode_value_multi(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(41, stream_len); + TEST_ASSERT_EQUAL_HEX(0x5BFFFBFF, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0xFC000000, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[3]); + + /* highest value with multi outlier encoding */ + data = INT32_MIN; model = 0; + stream_len = encode_value_multi(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(105, stream_len); + TEST_ASSERT_EQUAL_HEX(0x5BFFFBFF, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0xFC7FFFFF, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0xFF7FFFFF, bitstream[2]); + TEST_ASSERT_EQUAL_HEX(0xF7800000, bitstream[3]); + + /* small buffer error */ + data = 0; model = 38; + stream_len = encode_value_multi(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, stream_len); + + /* small buffer error when creating the multi escape symbol*/ + bitstream[0] = 0; + bitstream[1] = 0; + setup.max_bit_len = 32; + + stream_len = 32; + data = 31; model = 0; + stream_len = encode_value_multi(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, stream_len); + TEST_ASSERT_EQUAL_HEX(0, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0, bitstream[1]); +} -- GitLab From 721a8263099862b2927c9c0c12bc31dd46a0ed01 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Tue, 31 May 2022 15:19:12 +0200 Subject: [PATCH 20/46] add compression of non-imagette data --- cmp_tool.c | 411 +-- include/cmp_data_types.h | 350 ++- include/cmp_entity.h | 65 +- include/cmp_guess.h | 10 +- include/cmp_icu.h | 43 +- include/cmp_io.h | 10 +- include/cmp_rdcu.h | 28 +- include/cmp_support.h | 248 +- include/decmp.h | 8 +- lib/cmp_data_types.c | 936 ++---- lib/cmp_entity.c | 679 +++-- lib/cmp_guess.c | 127 +- lib/cmp_icu.c | 3207 ++++++++++++-------- lib/cmp_icu_new.c | 422 --- lib/cmp_io.c | 295 +- lib/cmp_rdcu.c | 586 ++-- lib/cmp_support.c | 919 +++--- lib/decmp.c | 1599 ++-------- lib/meson.build | 4 +- lib/rdcu_pkt_to_file.c | 14 +- meson.build | 2 + test/cmp_icu/meson.build | 26 +- test/cmp_icu/test_cmp_icu.c | 1454 ++++++++- test/cmp_icu/test_cmp_icu_new.c | 894 ------ test/cmp_icu/test_decmp.c | 442 +++ test/cmp_tool/cmp_tool_integration_test.py | 188 +- test/meson.build | 4 - 27 files changed, 6847 insertions(+), 6124 deletions(-) mode change 100755 => 100644 cmp_tool.c delete mode 100644 lib/cmp_icu_new.c delete mode 100644 test/cmp_icu/test_cmp_icu_new.c create mode 100644 test/cmp_icu/test_decmp.c diff --git a/cmp_tool.c b/cmp_tool.c old mode 100755 new mode 100644 index e3b2716..c30ae6e --- a/cmp_tool.c +++ b/cmp_tool.c @@ -28,6 +28,7 @@ #include "cmp_tool-config.h" #include "cmp_io.h" #include "cmp_icu.h" +#include "cmp_rdcu.h" /*TODO: shift setup to support */ #include "decmp.h" #include "cmp_guess.h" #include "cmp_entity.h" @@ -37,11 +38,21 @@ #define BUFFER_LENGTH_DEF_FAKTOR 2 - #define DEFAULT_MODEL_ID 53264 /* random default id */ #define DEFAULT_MODEL_COUNTER 0 +/* find a good set of compression parameters for a given dataset */ +static int guess_cmp_pars(struct cmp_cfg *cfg, const char *guess_cmp_mode, + int guess_level); + +/* compress the data and write the results to files */ +static int compression(struct cmp_cfg *cfg, struct cmp_info *info); + +/* decompress the data and write the results in file(s)*/ +static int decompression(struct cmp_entity *ent, uint16_t *input_model_buf); + + /* * For long options that have no equivalent short option, use a * non-character as a pseudo short option, starting with CHAR_MAX + 1. @@ -94,17 +105,6 @@ static int verbose_en; /* if non zero add a compression entity header in front of the compressed data */ static int include_cmp_header = 1; -/* find a good set of compression parameters for a given dataset */ -static int guess_cmp_pars(struct cmp_cfg *cfg, const char *guess_cmp_mode, - int guess_level); - -/* compress the data and write the results to files */ -static int compression(struct cmp_cfg *cfg, struct cmp_info *info); - -/* decompress the data and write the results in file(s)*/ -static int decompression(uint32_t *cmp_data_adr, uint16_t *input_model_buf, - struct cmp_info *info); - /* model ID string set by the --model_id option */ static const char *model_id_str; @@ -140,16 +140,16 @@ int main(int argc, char **argv) int guess_level = DEFAULT_GUESS_LEVEL; int print_diff_cfg = 0; - struct cmp_cfg cfg = {0}; /* compressor configuration struct */ - struct cmp_info info = {0}; /* decompression information struct */ - - /* buffer containing all read in compressed data for decompression (including header if used) */ - void *decomp_input_buf = NULL; - /* address to the compressed data for the decompression */ - uint32_t *cmp_data_adr = NULL; + /* buffer containing all read in compressed data for decompression */ + struct cmp_entity *decomp_entity = NULL; /* buffer containing the read in model */ uint16_t *input_model_buf = NULL; + struct cmp_info info = {0}; /* decompression information struct */ + struct cmp_cfg cfg = {0}; /* compressor configuration struct */ + + cfg.data_type = DATA_TYPE_IMAGETTE; /* use imagette as default data type */ + /* show help if no arguments are provided */ if (argc < 2) { print_help(program_name); @@ -190,7 +190,7 @@ int main(int argc, char **argv) verbose_en = 1; break; case 'V': /* --version */ - printf("%s version %s\n", PROGRAM_NAME, VERSION); + printf("%s version %s\n", PROGRAM_NAME, CMP_TOOL_VERSION); exit(EXIT_SUCCESS); break; case DIFF_CFG_OPTION: @@ -262,20 +262,38 @@ int main(int argc, char **argv) #endif if (print_model_cfg == 1) { - print_cfg(&DEFAULT_CFG_MODEL, print_rdcu_cfg); + cfg = rdcu_cfg_create(CMP_DEF_IMA_MODEL_DATA_TYPE, CMP_DEF_IMA_MODEL_CMP_MODE, + CMP_DEF_IMA_MODEL_MODEL_VALUE, CMP_DEF_IMA_MODEL_LOSSY_PAR); + rdcu_cfg_buffers(&cfg, NULL, 0, NULL, CMP_DEF_IMA_MODEL_RDCU_DATA_ADR, + CMP_DEF_IMA_MODEL_RDCU_MODEL_ADR, CMP_DEF_IMA_MODEL_RDCU_UP_MODEL_ADR, + CMP_DEF_IMA_MODEL_RDCU_BUFFER_ADR, 0); + rdcu_cfg_imagette(&cfg, + CMP_DEF_IMA_MODEL_GOLOMB_PAR, CMP_DEF_IMA_MODEL_SPILL_PAR, + CMP_DEF_IMA_MODEL_AP1_GOLOMB_PAR, CMP_DEF_IMA_MODEL_AP1_SPILL_PAR, + CMP_DEF_IMA_MODEL_AP2_GOLOMB_PAR, CMP_DEF_IMA_MODEL_AP2_SPILL_PAR); + print_cfg(&cfg, print_rdcu_cfg); exit(EXIT_SUCCESS); } if (print_diff_cfg == 1) { - print_cfg(&DEFAULT_CFG_DIFF, print_rdcu_cfg); + cfg = rdcu_cfg_create(CMP_DEF_IMA_DIFF_DATA_TYPE, CMP_DEF_IMA_DIFF_CMP_MODE, + CMP_DEF_IMA_DIFF_MODEL_VALUE, CMP_DEF_IMA_DIFF_LOSSY_PAR); + rdcu_cfg_buffers(&cfg, NULL, 0, NULL, CMP_DEF_IMA_DIFF_RDCU_DATA_ADR, + CMP_DEF_IMA_DIFF_RDCU_MODEL_ADR, CMP_DEF_IMA_DIFF_RDCU_UP_MODEL_ADR, + CMP_DEF_IMA_DIFF_RDCU_BUFFER_ADR, 0); + rdcu_cfg_imagette(&cfg, + CMP_DEF_IMA_DIFF_GOLOMB_PAR, CMP_DEF_IMA_DIFF_SPILL_PAR, + CMP_DEF_IMA_DIFF_AP1_GOLOMB_PAR, CMP_DEF_IMA_DIFF_AP1_SPILL_PAR, + CMP_DEF_IMA_DIFF_AP2_GOLOMB_PAR, CMP_DEF_IMA_DIFF_AP2_SPILL_PAR); + print_cfg(&cfg, print_rdcu_cfg); exit(EXIT_SUCCESS); } printf("#########################################################\n"); printf("### PLATO Compression/Decompression Tool Version %s ###\n", - VERSION); + CMP_TOOL_VERSION); printf("#########################################################\n"); - if (!strcmp(VERSION, "0.07") || !strcmp(VERSION, "0.08")) + if (!strcmp(CMP_TOOL_VERSION, "0.07") || !strcmp(CMP_TOOL_VERSION, "0.08")) printf("Info: Note that the behaviour of the cmp_tool has changed. From now on, the compressed data will be preceded by a header by default. The old behaviour can be achieved with the --no_header option.\n\n"); if (!data_file_name) { @@ -293,6 +311,7 @@ int main(int argc, char **argv) if (cmp_operation || guess_operation) { ssize_t size; + uint32_t input_size; if (cmp_operation) { printf("## Starting the compression ##\n"); @@ -308,21 +327,26 @@ int main(int argc, char **argv) printf("Importing data file %s ... ", data_file_name); /* count the samples in the data file when samples == 0 */ if (cfg.samples == 0) { - size = read_file16(data_file_name, NULL, 0, 0); - if (size <= 0) /* empty file is treated as an error */ + int samples; + size = read_file_data(data_file_name, cfg.data_type, NULL, 0, 0); + if (size <= 0 || size > UINT32_MAX) /* empty file is treated as an error */ goto fail; - cfg.samples = size/size_of_a_sample(cfg.cmp_mode); + samples = cmp_input_size_to_samples(size, cfg.data_type); + if (samples < 0) + goto fail; + cfg.samples = (uint32_t)samples; printf("\nNo samples parameter set. Use samples = %u.\n... ", cfg.samples); } - cfg.input_buf = malloc(cmp_cal_size_of_data(cfg.samples, cfg.cmp_mode)); + input_size = cmp_cal_size_of_data(cfg.samples, cfg.data_type); + cfg.input_buf = malloc(input_size); if (!cfg.input_buf) { fprintf(stderr, "%s: Error allocating memory for input data buffer.\n", PROGRAM_NAME); goto fail; } - size = read_file16(data_file_name, cfg.input_buf, cfg.samples, - verbose_en); + size = read_file_data(data_file_name, cfg.data_type, cfg.input_buf, + input_size, verbose_en); if (size < 0) goto fail; printf("DONE\n"); @@ -330,7 +354,8 @@ int main(int argc, char **argv) } else { /* decompression mode*/ printf("## Starting the decompression ##\n"); if (info_file_name) { - ssize_t size; + ssize_t f_size; + size_t ent_size; uint32_t cmp_size_byte; printf("Importing decompression information file %s ... ", info_file_name); @@ -341,63 +366,58 @@ int main(int argc, char **argv) printf("Importing compressed data file %s ... ", data_file_name); cmp_size_byte = cmp_bit_to_4byte(info.cmp_size); - cmp_data_adr = decomp_input_buf = malloc(cmp_size_byte); - if (!decomp_input_buf) { + + ent_size = cmp_ent_create(NULL, DATA_TYPE_IMAGETTE, info.cmp_mode_used == CMP_MODE_RAW, + cmp_size_byte); + if (ent_size <= 0) + goto fail; + decomp_entity = malloc(ent_size); + if (!decomp_entity) { fprintf(stderr, "%s: Error allocating memory for decompression input buffer.\n", PROGRAM_NAME); goto fail; } + ent_size = cmp_ent_create(decomp_entity, DATA_TYPE_IMAGETTE, info.cmp_mode_used == CMP_MODE_RAW, + cmp_size_byte); + if (ent_size <= 0) + goto fail; - size = read_file32(data_file_name, decomp_input_buf, - cmp_size_byte/4, verbose_en); - if (size < 0) + if (info.cmp_mode_used == CMP_MODE_RAW) + /* the raw data does not have to be a multiple of 4 bytes */ + cmp_size_byte = (info.cmp_size+7)/CHAR_BIT; + + f_size = read_file8(data_file_name, cmp_ent_get_data_buf(decomp_entity), + cmp_size_byte, verbose_en); + if (f_size < 0) + goto fail; + + error = cmp_ent_write_rdcu_cmp_pars(decomp_entity, &info, NULL); + if (error) goto fail; } else { /* read in compressed data with header */ ssize_t size; size_t buf_size; - struct cmp_entity *ent; printf("Importing compressed data file %s ... ", data_file_name); buf_size = size = read_file_cmp_entity(data_file_name, NULL, 0, 0); - if (size < 0) + if (size < 0 || size > UINT32_MAX) goto fail; /* to be save allocate at least the size of the cmp_entity struct */ if (buf_size < sizeof(struct cmp_entity)) buf_size = sizeof(struct cmp_entity); - decomp_input_buf = ent = malloc(buf_size); - if (!ent) { + decomp_entity = malloc(buf_size); + if (!decomp_entity) { fprintf(stderr, "%s: Error allocating memory for the compression entity buffer.\n", PROGRAM_NAME); goto fail; } - size = read_file_cmp_entity(data_file_name, ent, size, - verbose_en); - if (size < 0) - goto fail; - printf("DONE\n"); - - printf("Parse the compression entity header ... "); - error = cmp_ent_read_imagette_header(ent, &info); - if (error) - goto fail; - if (verbose_en) - print_cmp_info(&info); - - /* we reuse the entity buffer for the compressed data */ - cmp_data_adr = (uint32_t *)decomp_input_buf; - size = cmp_ent_get_cmp_data(ent, cmp_data_adr, buf_size); - ent = NULL; + size = read_file_cmp_entity(data_file_name, decomp_entity, + size, verbose_en); if (size < 0) goto fail; if (verbose_en) { - size_t i; - printf("\ncompressed data:\n"); - for (i = 0; i < size/sizeof(uint32_t); i++) { - printf("%08X ", cmp_data_adr[i]); - if (i && !((i+1) % 4)) - printf("\n"); - } + cmp_ent_print(decomp_entity); printf("\n"); } } @@ -406,10 +426,11 @@ int main(int argc, char **argv) /* read in model */ if ((cmp_operation && model_mode_is_used(cfg.cmp_mode)) || - (!cmp_operation && model_mode_is_used(info.cmp_mode_used)) || + (!cmp_operation && model_mode_is_used(cmp_ent_get_cmp_mode(decomp_entity))) || (guess_operation && model_file_name)) { ssize_t size; - uint32_t model_length; + uint32_t model_size; + enum cmp_data_type data_type; printf("Importing model file %s ... ", model_file_name ? model_file_name : ""); if (!model_file_name) { @@ -417,19 +438,22 @@ int main(int argc, char **argv) goto fail; } - if (cmp_operation || guess_operation) - model_length = cfg.samples; - else - model_length = info.samples_used; + if (cmp_operation || guess_operation) { + data_type = cfg.data_type; + model_size = cmp_cal_size_of_data(cfg.samples, cfg.data_type); + } else { + data_type = cmp_ent_get_data_type(decomp_entity); + model_size = cmp_ent_get_original_size(decomp_entity); + } - input_model_buf = malloc(model_length * size_of_a_sample(cfg.cmp_mode)); + input_model_buf = malloc(model_size); if (!input_model_buf) { fprintf(stderr, "%s: Error allocating memory for model buffer.\n", PROGRAM_NAME); goto fail; } - size = read_file16(model_file_name, input_model_buf, - model_length, verbose_en); + size = read_file_data(model_file_name, data_type, input_model_buf, + model_size, verbose_en); if (size < 0) goto fail; printf("DONE\n"); @@ -446,7 +470,7 @@ int main(int argc, char **argv) if (error) goto fail; } else { - error = decompression(cmp_data_adr, input_model_buf, &info); + error = decompression(decomp_entity, input_model_buf); if (error) goto fail; } @@ -454,17 +478,28 @@ int main(int argc, char **argv) /* write our the updated model for compressed or decompression */ if (!guess_operation && ((cmp_operation && model_mode_is_used(cfg.cmp_mode)) || - (!cmp_operation && model_mode_is_used(info.cmp_mode_used)))) { + (!cmp_operation && model_mode_is_used(cmp_ent_get_cmp_mode(decomp_entity))))) { + enum cmp_data_type data_type = DATA_TYPE_UNKOWN; + uint32_t model_size; + printf("Write updated model to file %s_upmodel.dat ... ", output_prefix); - error = write_to_file16(input_model_buf, info.samples_used, - output_prefix, "_upmodel.dat", verbose_en); + if (cmp_operation) { + data_type = cfg.data_type; + model_size = cmp_cal_size_of_data(cfg.samples, data_type); + } else { + data_type = cmp_ent_get_data_type(decomp_entity); + model_size = cmp_ent_get_original_size(decomp_entity); + } + + error = write_input_data_to_file(input_model_buf, model_size, data_type, + output_prefix, "_upmodel.dat", verbose_en); if (error) goto fail; printf("DONE\n"); } free(cfg.input_buf); - free(decomp_input_buf); + free(decomp_entity); free(input_model_buf); exit(EXIT_SUCCESS); @@ -473,56 +508,33 @@ fail: printf("FAILED\n"); free(cfg.input_buf); - free(decomp_input_buf); + free(decomp_entity); free(input_model_buf); exit(EXIT_FAILURE); } -static enum cmp_ent_data_type cmp_ent_map_cmp_mode_data_type(uint32_t cmp_mode) -{ - enum cmp_ent_data_type data_type; - - switch (cmp_mode) { - case MODE_RAW: - case MODE_MODEL_ZERO: - case MODE_DIFF_ZERO: - case MODE_MODEL_MULTI: - case MODE_DIFF_MULTI: - if (print_rdcu_cfg) - data_type = DATA_TYPE_IMAGETTE_ADAPTIVE; - else - data_type = DATA_TYPE_IMAGETTE; - break; - default: - printf("No mapping between compression mode and header data type\n!"); - return DATA_TYPE_UNKOWN; - } - - /* set raw bit if needed */ - if (raw_mode_is_used(cmp_mode)) - data_type |= 1UL << RAW_BIT_DATA_TYPE_POS; - - return data_type; -} - - /* find a good set of compression parameters for a given dataset */ static int guess_cmp_pars(struct cmp_cfg *cfg, const char *guess_cmp_mode, int guess_level) { int error; uint32_t cmp_size_bit; - float cr; + double cr; printf("Search for a good set of compression parameters (level: %d) ... ", guess_level); if (!strcmp(guess_cmp_mode, "RDCU")) { + if (print_rdcu_cfg) + cfg->data_type = DATA_TYPE_IMAGETTE_ADAPTIVE; + else + cfg->data_type = DATA_TYPE_IMAGETTE; if (cfg->model_buf) cfg->cmp_mode = CMP_GUESS_DEF_MODE_MODEL; else cfg->cmp_mode = CMP_GUESS_DEF_MODE_DIFF; } else { + cfg->data_type = DATA_TYPE_IMAGETTE; /* TODO*/ error = cmp_mode_parse(guess_cmp_mode, &cfg->cmp_mode); if (error) { fprintf(stderr, "%s: Error: unknown compression mode: %s\n", PROGRAM_NAME, guess_cmp_mode); @@ -540,7 +552,7 @@ static int guess_cmp_pars(struct cmp_cfg *cfg, const char *guess_cmp_mode, if (include_cmp_header) cmp_size_bit = CHAR_BIT * (cmp_bit_to_4byte(cmp_size_bit) + - cmp_ent_cal_hdr_size(cmp_ent_map_cmp_mode_data_type(cfg->cmp_mode))); + cmp_ent_cal_hdr_size(cfg->data_type, cfg->cmp_mode == CMP_MODE_RAW)); printf("DONE\n"); @@ -550,7 +562,7 @@ static int guess_cmp_pars(struct cmp_cfg *cfg, const char *guess_cmp_mode, return -1; printf("DONE\n"); - cr = (8.0 * cfg->samples * size_of_a_sample(cfg->cmp_mode))/cmp_size_bit; + cr = (8.0 * cmp_cal_size_of_data(cfg->samples, cfg->data_type))/cmp_size_bit; printf("Guessed parameters can compress the data with a CR of %.2f.\n", cr); return 0; @@ -594,48 +606,58 @@ static int gen_rdcu_write_pkts(struct cmp_cfg *cfg) } -/* add a compression entity header in front of the data */ -static int add_cmp_ent_hdr(struct cmp_cfg *cfg, struct cmp_info *info, - uint64_t start_time) +/** + * @brief generate the compression information used based on the compression + * configuration, to emulate the RDCU behaviour + * + * @param cfg compression configuration struct + * @param cmp_size_bit length of the bitstream in bits + * @param info compressor information struct to set the used compression + * parameters (can be NULL) + * + * @note set cmp_size, ap1_cmp_size, ap2_cmp_size will be set to 0 + * + * @returns 0 on success, error otherwise + * TODO: set cmp_err in error case + */ + +static int cmp_gernate_rdcu_info(const struct cmp_cfg *cfg, int cmp_size_bit, struct cmp_info *info) { - int error; - uint32_t red_val; - uint8_t model_counter = DEFAULT_MODEL_COUNTER; - uint16_t model_id = DEFAULT_MODEL_ID; - size_t s, cmp_hdr_size; - struct cmp_entity *ent; - enum cmp_ent_data_type data_type = cmp_ent_map_cmp_mode_data_type(cfg->cmp_mode); + if (!cfg) + return -1; - if (model_id_str) { - error = atoui32("model_id", model_id_str, &red_val); - if (error || red_val > UINT16_MAX) - return -1; - model_id = red_val; - } - if (model_counter_str) { - error = atoui32("model_counter", model_counter_str, &red_val); - if (error || red_val > UINT8_MAX) - return -1; - model_counter = red_val; - } else { - if (model_mode_is_used(cfg->cmp_mode)) - model_counter = DEFAULT_MODEL_COUNTER + 1; - } + if (cfg->cmp_mode > UINT8_MAX) + return -1; - cmp_hdr_size = cmp_ent_cal_hdr_size(data_type); - if (!cmp_hdr_size) + if (cfg->round > UINT8_MAX) return -1; - memmove((uint8_t *)cfg->icu_output_buf+cmp_hdr_size, cfg->icu_output_buf, - cmp_bit_to_4byte(info->cmp_size)); - ent = (struct cmp_entity *)cfg->icu_output_buf; - s = cmp_ent_build(ent, data_type, cmp_tool_gen_version_id(VERSION), - start_time, cmp_ent_create_timestamp(NULL), model_id, - model_counter, info, cfg); - if (!s) { - fprintf(stderr, "%s: error occurred while creating the compression entity header.\n", PROGRAM_NAME); + if (cfg->model_value > UINT8_MAX) return -1; + + if (info) { + info->cmp_err = 0; + info->cmp_mode_used = (uint8_t)cfg->cmp_mode; + info->model_value_used = (uint8_t)cfg->model_value; + info->round_used = (uint8_t)cfg->round; + info->spill_used = cfg->spill; + info->golomb_par_used = cfg->golomb_par; + info->samples_used = cfg->samples; + info->cmp_size = 0; + info->ap1_cmp_size = 0; + info->ap2_cmp_size = 0; + info->rdcu_new_model_adr_used = cfg->rdcu_new_model_adr; + info->rdcu_cmp_adr_used = cfg->rdcu_buffer_adr; + + if (cmp_size_bit == CMP_ERROR_SAMLL_BUF) + /* the icu_output_buf is to small to store the whole bitstream */ + info->cmp_err |= 1UL << SMALL_BUFFER_ERR_BIT; /* set small buffer error */ + if (cmp_size_bit < 0) + info->cmp_size = 0; + else + info->cmp_size = (uint32_t)cmp_size_bit; } + return 0; } @@ -643,12 +665,14 @@ static int add_cmp_ent_hdr(struct cmp_cfg *cfg, struct cmp_info *info, /* compress the data and write the results to files */ static int compression(struct cmp_cfg *cfg, struct cmp_info *info) { - int error; - uint32_t cmp_size_byte; - size_t out_buf_size; + int cmp_size, error; + uint32_t cmp_size_byte, out_buf_size; + size_t s; uint64_t start_time = cmp_ent_create_timestamp(NULL); - - cfg->icu_output_buf = NULL; + struct cmp_entity *cmp_entity = NULL; + uint8_t model_counter = DEFAULT_MODEL_COUNTER; + uint16_t model_id = DEFAULT_MODEL_ID; + void *data_to_write_to_file; if (cfg->buffer_length == 0) { cfg->buffer_length = (cfg->samples+1) * BUFFER_LENGTH_DEF_FAKTOR; /* +1 to prevent malloc(0)*/ @@ -666,30 +690,65 @@ static int compression(struct cmp_cfg *cfg, struct cmp_info *info) printf("Compress data ... "); /* round up to a multiple of 4 */ - out_buf_size = (cmp_cal_size_of_data(cfg->buffer_length, cfg->cmp_mode) + 3) & ~0x3U; + out_buf_size = (cmp_cal_size_of_data(cfg->buffer_length, cfg->data_type) + 3) & ~0x3U; - cfg->icu_output_buf = malloc(out_buf_size + sizeof(struct cmp_entity)); - if (cfg->icu_output_buf == NULL) { + cmp_entity = calloc(1, out_buf_size + sizeof(struct cmp_entity)); + if (cmp_entity == NULL) { fprintf(stderr, "%s: Error allocating memory for output buffer.\n", PROGRAM_NAME); goto error_cleanup; } + s = cmp_ent_create(cmp_entity, cfg->data_type, cfg->cmp_mode == CMP_MODE_RAW, out_buf_size); + if (!s) { + fprintf(stderr, "%s: error occurred while creating the compression entity header.\n", PROGRAM_NAME); + goto error_cleanup; + } + cfg->icu_output_buf = cmp_ent_get_data_buf(cmp_entity); - error = icu_compress_data(cfg, info); - if (error || info->cmp_err != 0) { - printf("\nCompression error 0x%02X\n... ", info->cmp_err); + cmp_size = icu_compress_data(cfg); + cmp_gernate_rdcu_info(cfg, cmp_size, info); + if (cmp_size < 0 || info->cmp_err != 0) { + if (info->cmp_err) + printf("\nCompression error 0x%02X\n... ", info->cmp_err); /* TODO: add a parse cmp error function */ /* if ((info->cmp_err >> SMALL_BUFFER_ERR_BIT) & 1U) */ /* fprintf(stderr, "%s: the buffer for the compressed data is too small. Try a larger buffer_length parameter.\n", PROGRAM_NAME); */ goto error_cleanup; } + if (model_id_str) { + uint32_t red_val; + error = atoui32("model_id", model_id_str, &red_val); + if (error || red_val > UINT16_MAX) + return -1; + model_id = (uint16_t)red_val; + } + if (model_counter_str) { + uint32_t red_val; + error = atoui32("model_counter", model_counter_str, &red_val); + if (error || red_val > UINT8_MAX) + return -1; + model_counter = (uint8_t)red_val; + } else { + if (model_mode_is_used(cfg->cmp_mode)) + model_counter = DEFAULT_MODEL_COUNTER + 1; + } + + s = cmp_ent_build(cmp_entity, cmp_tool_gen_version_id(CMP_TOOL_VERSION), + start_time, cmp_ent_create_timestamp(NULL), model_id, + model_counter, cfg, cmp_size); + if (!s) { + fprintf(stderr, "%s: error occurred while creating the compression entity header.\n", PROGRAM_NAME); + goto error_cleanup; + } if (include_cmp_header) { - error = add_cmp_ent_hdr(cfg, info, start_time); - if (error) - goto error_cleanup; - cmp_size_byte = cmp_ent_get_size((struct cmp_entity *)cfg->icu_output_buf); + data_to_write_to_file = cmp_entity; + cmp_size_byte = cmp_ent_get_size(cmp_entity); } else { - cmp_size_byte = cmp_bit_to_4byte(info->cmp_size); + data_to_write_to_file = cmp_ent_get_data_buf(cmp_entity); + if (cfg->cmp_mode == CMP_MODE_RAW) + cmp_size_byte = info->cmp_size/CHAR_BIT; + else + cmp_size_byte = cmp_bit_to_4byte(info->cmp_size); } printf("DONE\n"); @@ -703,7 +762,7 @@ static int compression(struct cmp_cfg *cfg, struct cmp_info *info) } printf("Write compressed data to file %s.cmp ... ", output_prefix); - error = write_cmp_data_file(cfg->icu_output_buf, cmp_size_byte, + error = write_cmp_data_file(data_to_write_to_file, cmp_size_byte, output_prefix, ".cmp", verbose_en); if (error) goto error_cleanup; @@ -724,52 +783,56 @@ static int compression(struct cmp_cfg *cfg, struct cmp_info *info) printf("\n"); } - free(cfg->icu_output_buf); + free(cmp_entity); cfg->icu_output_buf = NULL; return 0; error_cleanup: - free(cfg->icu_output_buf); + free(cmp_entity); cfg->icu_output_buf = NULL; + return -1; } /* decompress the data and write the results in file(s)*/ -static int decompression(uint32_t *cmp_data_adr, uint16_t *input_model_buf, - struct cmp_info *info) +static int decompression(struct cmp_entity *ent, uint16_t *input_model_buf) { int error; + int decomp_size; uint16_t *decomp_output; printf("Decompress data ... "); - if (info->samples_used == 0) { + decomp_size = decompress_cmp_entiy(ent, input_model_buf, input_model_buf, NULL); + if (decomp_size < 0) + return -1; + if (decomp_size == 0) { printf("\nWarring: No data are decompressed.\n... "); printf("DONE\n"); return 0; } - decomp_output = malloc(cmp_cal_size_of_data(info->samples_used, - info->cmp_mode_used)); + decomp_output = malloc((size_t)decomp_size); if (decomp_output == NULL) { fprintf(stderr, "%s: Error allocating memory for decompressed data.\n", PROGRAM_NAME); return -1; } - error = decompress_data(cmp_data_adr, input_model_buf, info, - decomp_output); - if (error) { + decomp_size = decompress_cmp_entiy(ent, input_model_buf, input_model_buf, decomp_output); + if (decomp_size <= 0) { free(decomp_output); return -1; } + printf("DONE\n"); printf("Write decompressed data to file %s.dat ... ", output_prefix); - error = write_to_file16(decomp_output, info->samples_used, - output_prefix, ".dat", verbose_en); + error = write_input_data_to_file(decomp_output, (uint32_t)decomp_size, cmp_ent_get_data_type(ent), + output_prefix, ".dat", verbose_en); + free(decomp_output); if (error) return -1; diff --git a/include/cmp_data_types.h b/include/cmp_data_types.h index 2ec635e..75b69be 100644 --- a/include/cmp_data_types.h +++ b/include/cmp_data_types.h @@ -14,17 +14,20 @@ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * + * @see for N-DPU packed definition: PLATO-LESIA-PL-RP-0031 Issue: 1.9 (N-DPU->ICU data rate) + * @see for calculation of the max used bits: PLATO-LESIA-PDC-TN-0054 Issue: 1.7 * - * Three data rates: + * Three data rates (for N-DPU): * fast cadence (nominally 25s) * short cadence (nominally 50s) * long cadence (nominally 600s) * * The science products are identified as this: - * FX = normal light flux - * NCOB = normal center of brightness - * EFX = extended flux - * ECOB = extended center of brightness + * exp_flags = selected exposure flags + * fx = normal light flux + * ncob = normal center of brightness + * efx = extended flux + * ecob = extended center of brightness * The prefixes F, S and L stand for Fast, Short and Long cadence */ @@ -34,173 +37,240 @@ #include <stdint.h> #include "compiler.h" +#include "cmp_support.h" + +#define MAX_USED_NC_IMAGETTE_BITS 16 +#define MAX_USED_SATURATED_IMAGETTE_BITS 16 /* TBC */ +#define MAX_USED_FC_IMAGETTE_BITS 16 /* TBC */ + +#define MAX_USED_F_FX_BITS 21 /* max exp. int value: (1.078*10^5)/0.1 = 1,078,000 -> 21 bits */ +#define MAX_USED_F_EFX_BITS MAX_USED_F_FX_BITS /* we use the same as f_fx */ +#define MAX_USED_F_NCOB_BITS 20 /* max exp. int value: 6/10^−5 = 6*10^5 -> 20 bits */ +#define MAX_USED_F_ECOB_BITS 32 /* TBC */ + +#define MAX_USED_S_FX_EXPOSURE_FLAGS_BITS 2 /* 2 flags + 6 spare bits */ +#define MAX_USED_S_FX_BITS 24 /* max exp. int value: (1.078*10^5-34.71)/0.01 = 10,780,000-> 24 bits */ +#define MAX_USED_S_EFX_BITS MAX_USED_S_FX_BITS /* we use the same as s_fx */ +#define MAX_USED_S_NCOB_BITS MAX_USED_F_NCOB_BITS +#define MAX_USED_S_ECOB_BITS 32 /* TBC */ + +#define MAX_USED_L_FX_EXPOSURE_FLAGS_BITS 24 /* 24 flags */ +#define MAX_USED_L_FX_BITS MAX_USED_S_FX_BITS +#define MAX_USED_L_FX_VARIANCE_BITS 32 /* no maximum value is given in PLATO-LESIA-PDC-TN-0054 */ +#define MAX_USED_L_EFX_BITS MAX_USED_L_FX_BITS /* we use the same as l_fx */ +#define MAX_USED_L_NCOB_BITS MAX_USED_F_NCOB_BITS +#define MAX_USED_L_ECOB_BITS 32 /* TBC */ +#define MAX_USED_L_COB_VARIANCE_BITS 25 /* max exp int value: 0.1739/10^−8 = 17390000 -> 25 bits */ + +#define MAX_USED_NC_OFFSET_MEAN_BITS 2 /* no maximum value is given in PLATO-LESIA-PDC-TN-0054 */ +#define MAX_USED_NC_OFFSET_VARIANCE_BITS 10 /* max exp. int value: 9.31/0.01 = 931 -> 10 bits */ + +#define MAX_USED_NC_BACKGROUND_MEAN_BITS 16 /* max exp. int value: (391.8-(-50))/0.01 = 44,180 -> 16 bits */ +#define MAX_USED_NC_BACKGROUND_VARIANCE_BITS 16 /* max exp. int value: 6471/0.1 = 64710 -> 16 bit */ +#define MAX_USED_NC_BACKGROUND_OUTLIER_PIXELS_BITS 5 /* maximum = 16 -> 5 bits */ + +#define MAX_USED_SMEARING_MEAN_BITS 15 /* max exp. int value: (219.9 - -50)/0.01 = 26.990 */ +#define MAX_USED_SMEARING_VARIANCE_MEAN_BITS 16 /* no maximum value is given in PLATO-LESIA-PDC-TN-0054 */ +#define MAX_USED_SMEARING_OUTLIER_PIXELS_BITS 11 /* maximum = 1200 -> 11 bits */ + +#define MAX_USED_FC_OFFSET_MEAN_BITS 32 /* no maximum value is given in PLATO-LESIA-PDC-TN-0054 */ +#define MAX_USED_FC_OFFSET_VARIANCE_BITS 9 /* max exp. int value: 342/1 = 342 -> 9 bits */ +#define MAX_USED_FC_OFFSET_PIXEL_IN_ERROR_BITS 16 /* TBC */ + +#define MAX_USED_FC_BACKGROUND_MEAN_BITS 10 /* max exp. int value: (35.76-(-50))/0.1 = 858 -> 10 bits*/ +#define MAX_USED_FC_BACKGROUND_VARIANCE_BITS 6 /* max exp. int value: 53.9/1 = 54 -> 6 bits */ +#define MAX_USED_FC_BACKGROUND_OUTLIER_PIXELS_BITS 16 /* TBC */ + + +/* struct holding the maximum length of the different data products types in bits */ +struct cmp_max_used_bits { + unsigned int version; + unsigned int s_exp_flags; + unsigned int s_fx; + unsigned int s_efx; + unsigned int s_ncob; /* s_ncob_x and s_ncob_y */ + unsigned int s_ecob; /* s_ecob_x and s_ncob_y */ + unsigned int f_fx; + unsigned int f_efx; + unsigned int f_ncob; /* f_ncob_x and f_ncob_y */ + unsigned int f_ecob; /* f_ecob_x and f_ncob_y */ + unsigned int l_exp_flags; + unsigned int l_fx; + unsigned int l_fx_variance; + unsigned int l_efx; + unsigned int l_ncob; /* l_ncob_x and l_ncob_y */ + unsigned int l_ecob; /* l_ecob_x and l_ncob_y */ + unsigned int l_cob_variance; /* l_cob_x_variance and l_cob_y_variance */ + unsigned int nc_imagette; + unsigned int saturated_imagette; + unsigned int nc_offset_mean; + unsigned int nc_offset_variance; + unsigned int nc_background_mean; + unsigned int nc_background_variance; + unsigned int nc_background_outlier_pixels; + unsigned int smeating_mean; + unsigned int smeating_variance_mean; + unsigned int smearing_outlier_pixels; + unsigned int fc_imagette; + unsigned int fc_offset_mean; + unsigned int fc_offset_variance; + unsigned int fc_offset_pixel_in_error; + unsigned int fc_background_mean; + unsigned int fc_background_variance; + unsigned int fc_background_outlier_pixels; +}; + + +/* Set and read the max_used_bits, which specify how many bits are needed to + * represent the highest possible value. + */ +void cmp_set_max_used_bits(const struct cmp_max_used_bits *set_max_used_bits); +struct cmp_max_used_bits cmp_get_max_used_bits(void); + +/* for internal use only! */ +extern struct cmp_max_used_bits max_used_bits; -#define MODE_RAW_S_FX 100 -#define MODE_MODEL_ZERO_S_FX 101 -#define MODE_DIFF_ZERO_S_FX 102 -#define MODE_MODEL_MULTI_S_FX 103 -#define MODE_DIFF_MULTI_S_FX 104 - -#define MODE_MODEL_ZERO_S_FX_EFX 110 -#define MODE_DIFF_ZERO_S_FX_EFX 111 -#define MODE_MODEL_MULTI_S_FX_EFX 112 -#define MODE_DIFF_MULTI_S_FX_EFX 113 - -#define MODE_MODEL_ZERO_S_FX_NCOB 120 -#define MODE_DIFF_ZERO_S_FX_NCOB 121 -#define MODE_MODEL_MULTI_S_FX_NCOB 122 -#define MODE_DIFF_MULTI_S_FX_NCOB 123 - -#define MODE_MODEL_ZERO_S_FX_EFX_NCOB_ECOB 130 -#define MODE_DIFF_ZERO_S_FX_EFX_NCOB_ECOB 131 -#define MODE_MODEL_MULTI_S_FX_EFX_NCOB_ECOB 132 -#define MODE_DIFF_MULTI_S_FX_EFX_NCOB_ECOB 133 - -#define MODE_MODEL_ZERO_F_FX 140 -#define MODE_DIFF_ZERO_F_FX 141 -#define MODE_MODEL_MULTI_F_FX 142 -#define MODE_DIFF_MULTI_F_FX 143 - -#define MODE_MODEL_ZERO_F_FX_EFX 150 -#define MODE_DIFF_ZERO_F_FX_EFX 151 -#define MODE_MODEL_MULTI_F_FX_EFX 152 -#define MODE_DIFF_MULTI_F_FX_EFX 153 - -#define MODE_MODEL_ZERO_F_FX_NCOB 160 -#define MODE_DIFF_ZERO_F_FX_NCOB 161 -#define MODE_MODEL_MULTI_F_FX_NCOB 162 -#define MODE_DIFF_MULTI_F_FX_NCOB 163 - -#define MODE_MODEL_ZERO_F_FX_EFX_NCOB_ECOB 170 -#define MODE_DIFF_ZERO_F_FX_EFX_NCOB_ECOB 171 -#define MODE_MODEL_MULTI_F_FX_EFX_NCOB_ECOB 172 -#define MODE_DIFF_MULTI_F_FX_EFX_NCOB_ECOB 173 - -#define MODE_RAW_32 200 -#define MODE_DIFF_ZERO_32 201 -#define MODE_DIFF_MULTI_32 202 -#define MODE_MODEL_ZERO_32 203 -#define MODE_MODEL_MULTI_32 204 - -int lossy_rounding_16(uint16_t *data_buf, unsigned int samples, unsigned int - round); -int de_lossy_rounding_16(uint16_t *data_buf, uint32_t samples_used, uint32_t - round_used); -int lossy_rounding_32(uint32_t *data_buf, unsigned int samples, unsigned int - round); -int de_lossy_rounding_32(uint32_t *data_buf, uint32_t samples_used, uint32_t - round_used); +/* Source data header structure for multi entry packet */ +#define MULTI_ENTRY_HDR_SIZE 12 +compile_time_assert(MULTI_ENTRY_HDR_SIZE % sizeof(uint32_t) == 0, N_DPU_ICU_MULTI_ENTRY_HDR_NOT_4_BYTE_ALLIED); -void cmp_input_big_to_cpu_endiannessy(void *data, u_int32_t data_size_byte, - uint32_t cmp_mode); +struct multi_entry_hdr { + uint32_t timestamp_coarse; + uint16_t timestamp_fine; + uint16_t configuration_id; + uint16_t collection_id; + uint16_t collection_length; + uint8_t entry[]; +} __attribute__((packed)); +compile_time_assert(sizeof(struct multi_entry_hdr) == MULTI_ENTRY_HDR_SIZE, N_DPU_ICU_MULTI_ENTRY_HDR_SIZE_IS_NOT_CORRECT); -/* @see for packed definition: PLATO-LESIA-PL-RP-0031 Issue: 1.9 (N-DPU->ICU data rate) */ -#define N_DPU_ICU_MULTI_ENTRY_HDR_SIZE 12 -struct n_dpu_icu_multi_entry_hdr { - uint64_t ncxx_timestamp:48; - uint16_t ncxx_configuration_id; - uint16_t ncxx_collection_id; - uint16_t ncxx_collection_length; +struct s_fx { + uint8_t exp_flags; /* selected exposure flags (2 flags + 6 spare bits) */ + uint32_t fx; /* normal light flux */ } __attribute__((packed)); -compile_time_assert(sizeof(struct n_dpu_icu_multi_entry_hdr) == N_DPU_ICU_MULTI_ENTRY_HDR_SIZE, N_DPU_ICU_MULTI_ENTRY_HDR_SIZE_IS_NOT_CORRECT); -struct S_FX { - uint8_t EXPOSURE_FLAGS; - uint32_t FX; +struct s_fx_efx { + uint8_t exp_flags; + uint32_t fx; + uint32_t efx; } __attribute__((packed)); -struct S_FX sub_S_FX(struct S_FX a, struct S_FX b); -struct S_FX add_S_FX(struct S_FX a, struct S_FX b); -int lossy_rounding_S_FX(struct S_FX *data_buf, unsigned int samples, - unsigned int round); -int de_lossy_rounding_S_FX(struct S_FX *data_buf, unsigned int samples_used, - unsigned int round_used); -struct S_FX cal_up_model_S_FX(struct S_FX data_buf, struct S_FX model_buf, - unsigned int model_value); + +struct s_fx_ncob { + uint8_t exp_flags; + uint32_t fx; + uint32_t ncob_x; + uint32_t ncob_y; +} __attribute__((packed)); -struct S_FX_EFX { - uint8_t EXPOSURE_FLAGS; - uint32_t FX; - uint32_t EFX; +struct s_fx_efx_ncob_ecob { + uint8_t exp_flags; + uint32_t fx; + uint32_t ncob_x; + uint32_t ncob_y; + uint32_t efx; + uint32_t ecob_x; + uint32_t ecob_y; } __attribute__((packed)); -struct S_FX_EFX sub_S_FX_EFX(struct S_FX_EFX a, struct S_FX_EFX b); -struct S_FX_EFX add_S_FX_EFX(struct S_FX_EFX a, struct S_FX_EFX b); -int lossy_rounding_S_FX_EFX(struct S_FX_EFX *data, unsigned int samples, - unsigned int round); -int de_lossy_rounding_S_FX_EFX(struct S_FX_EFX *data_buf, unsigned int - samples_used, unsigned int round_used); -struct S_FX_EFX cal_up_model_S_FX_EFX(struct S_FX_EFX data_buf, struct S_FX_EFX - model_buf, unsigned int model_value); +struct f_fx { + uint32_t fx; +} __attribute__((packed)); -struct S_FX_NCOB { - uint8_t EXPOSURE_FLAGS; - uint32_t FX; - uint32_t NCOB_X; - uint32_t NCOB_Y; + +struct f_fx_efx { + uint32_t fx; + uint32_t efx; } __attribute__((packed)); -struct S_FX_NCOB sub_S_FX_NCOB(struct S_FX_NCOB a, struct S_FX_NCOB b); -struct S_FX_NCOB add_S_FX_NCOB(struct S_FX_NCOB a, struct S_FX_NCOB b); -int lossy_rounding_S_FX_NCOB(struct S_FX_NCOB *data_buf, unsigned int samples, - unsigned int round); -int de_lossy_rounding_S_FX_NCOB(struct S_FX_NCOB *data_buf, unsigned int - samples_used, unsigned int round_used); -struct S_FX_NCOB cal_up_model_S_FX_NCOB(struct S_FX_NCOB data_buf, - struct S_FX_NCOB model_buf, - unsigned int model_value); + +struct f_fx_ncob { + uint32_t fx; + uint32_t ncob_x; + uint32_t ncob_y; +} __attribute__((packed)); -struct S_FX_EFX_NCOB_ECOB { - uint8_t EXPOSURE_FLAGS; - uint32_t FX; - uint32_t NCOB_X; - uint32_t NCOB_Y; - uint32_t EFX; - uint32_t ECOB_X; - uint32_t ECOB_Y; +struct f_fx_efx_ncob_ecob { + uint32_t fx; + uint32_t ncob_x; + uint32_t ncob_y; + uint32_t efx; + uint32_t ecob_x; + uint32_t ecob_y; } __attribute__((packed)); -struct S_FX_EFX_NCOB_ECOB sub_S_FX_EFX_NCOB_ECOB(struct S_FX_EFX_NCOB_ECOB a, - struct S_FX_EFX_NCOB_ECOB b); -struct S_FX_EFX_NCOB_ECOB add_S_FX_EFX_NCOB_ECOB(struct S_FX_EFX_NCOB_ECOB a, - struct S_FX_EFX_NCOB_ECOB b); -int lossy_rounding_S_FX_EFX_NCOB_ECOB(struct S_FX_EFX_NCOB_ECOB *data_buf, - unsigned int samples, unsigned int round); -int de_lossy_rounding_S_FX_EFX_NCOB_ECOB(struct S_FX_EFX_NCOB_ECOB *data_buf, - unsigned int samples, - unsigned int round); -struct F_FX { - uint32_t FX; +struct l_fx { + uint32_t exp_flags:24; /* selected exposure flags (24 flags) */ + uint32_t fx; + uint32_t fx_variance; } __attribute__((packed)); -struct F_FX_EFX { - uint32_t FX; - uint32_t EFX; +struct l_fx_efx { + uint32_t exp_flags:24; /* selected exposure flags (24 flags) */ + uint32_t fx; + uint32_t efx; + uint32_t fx_variance; } __attribute__((packed)); -struct F_FX_NCOB { - uint32_t FX; - uint32_t NCOB_X; - uint32_t NCOB_Y; +struct l_fx_ncob { + uint32_t exp_flags:24; /* selected exposure flags (24 flags) */ + uint32_t fx; + uint32_t ncob_x; + uint32_t ncob_y; + uint32_t fx_variance; + uint32_t cob_x_variance; + uint32_t cob_y_variance; } __attribute__((packed)); -struct F_FX_EFX_NCOB_ECOB { - uint32_t FX; - uint32_t NCOB_X; - uint32_t NCOB_Y; - uint32_t EFX; - uint32_t ECOB_X; - uint32_t ECOB_Y; +struct l_fx_efx_ncob_ecob { + uint32_t exp_flags:24; /* selected exposure flags (24 flags) */ + uint32_t fx; + uint32_t ncob_x; + uint32_t ncob_y; + uint32_t efx; + uint32_t ecob_x; + uint32_t ecob_y; + uint32_t fx_variance; + uint32_t cob_x_variance; + uint32_t cob_y_variance; } __attribute__((packed)); + +struct nc_offset { + uint32_t mean; + uint32_t variance; +} __attribute__((packed)); + + +struct nc_background { + uint32_t mean; + uint32_t variance; + uint16_t outlier_pixels; +} __attribute__((packed)); + + +struct smearing { + uint32_t mean; + uint16_t variance_mean; + uint16_t outlier_pixels; +} __attribute__((packed)); + + +size_t size_of_a_sample(enum cmp_data_type data_type); +unsigned int cmp_cal_size_of_data(unsigned int samples, enum cmp_data_type data_type); +int cmp_input_size_to_samples(unsigned int size, enum cmp_data_type data_type); + +int cmp_input_big_to_cpu_endianness(void *data, u_int32_t data_size_byte, + enum cmp_data_type data_type); + #endif /* CMP_DATA_TYPE_H */ diff --git a/include/cmp_entity.h b/include/cmp_entity.h index 2d6bf54..a06715f 100644 --- a/include/cmp_entity.h +++ b/include/cmp_entity.h @@ -33,34 +33,6 @@ #include "cmp_support.h" -/* Defined Compression Data Product Types */ -enum cmp_ent_data_type { - DATA_TYPE_IMAGETTE = 1, - DATA_TYPE_IMAGETTE_ADAPTIVE, - DATA_TYPE_SAT_IMAGETTE, - DATA_TYPE_SAT_IMAGETTE_ADAPTIVE, - DATA_TYPE_OFFSET, - DATA_TYPE_BACKGROUND, - DATA_TYPE_SMEARING, - DATA_TYPE_S_FX, - DATA_TYPE_S_FX_DFX, - DATA_TYPE_S_FX_NCOB, - DATA_TYPE_S_FX_DFX_NCOB_ECOB, - DATA_TYPE_L_FX, - DATA_TYPE_L_FX_DFX, - DATA_TYPE_L_FX_NCOB, - DATA_TYPE_L_FX_DFX_NCOB_ECOB, - DATA_TYPE_F_FX, - DATA_TYPE_F_FX_DFX, - DATA_TYPE_F_FX_NCOB, - DATA_TYPE_F_FX_DFX_NCOB_ECOB, - DATA_TYPE_F_CAM_IMAGETTE, - DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE, - DATA_TYPE_F_CAM_OFFSET, - DATA_TYPE_F_CAM_BACKGROUND, - DATA_TYPE_UNKOWN = 0x7FFF, -}; - #define GENERIC_HEADER_SIZE 32 #define SPECIFIC_IMAGETTE_HEADER_SIZE 4 #define SPECIFIC_IMAGETTE_ADAPTIVE_HEADER_SIZE 12 @@ -155,21 +127,31 @@ compile_time_assert(sizeof(struct cmp_entity) == NON_IMAGETTE_HEADER_SIZE, CMP_E -/* brief create a compression entity by setting the size of the - * compression entity and the data product type in the entity header +/* create a compression entity by setting the size of the compression entity and + * the data product type in the entity header */ -size_t cmp_ent_create(struct cmp_entity *ent, enum cmp_ent_data_type data_type, - uint32_t cmp_size_byte); +uint32_t cmp_ent_create(struct cmp_entity *ent, enum cmp_data_type data_type, + int raw_mode_flag, uint32_t cmp_size_byte); /* create a compression entity and set the header fields */ -size_t cmp_ent_build(struct cmp_entity *ent, enum cmp_ent_data_type data_type, - uint32_t version_id, uint64_t start_time, - uint64_t end_time, uint16_t model_id, uint8_t model_counter, - struct cmp_info *info, struct cmp_cfg *cfg); +size_t cmp_ent_build(struct cmp_entity *ent, uint32_t version_id, + uint64_t start_time, uint64_t end_time, uint16_t model_id, + uint8_t model_counter, struct cmp_cfg *cfg, int cmp_size_bits); + +/* read in a compression entity header */ +int cmp_ent_read_header(struct cmp_entity *ent, struct cmp_cfg *cfg); -/* read in a imagette compression entity header to a info struct */ -int cmp_ent_read_imagette_header(struct cmp_entity *ent, struct cmp_info *info); +/* write the compression parameters from a compression configuration into the + * compression entity header + */ +int cmp_ent_write_cmp_pars(struct cmp_entity *ent, const struct cmp_cfg *cfg, + int cmp_size_bits); +/* write the parameters from the RDCU decompression information structure in the + * compression entity header + */ +int cmp_ent_write_rdcu_cmp_pars(struct cmp_entity *ent, const struct cmp_info *info, + const struct cmp_cfg *cfg); /* set functions for generic compression entity header */ @@ -187,8 +169,7 @@ int cmp_ent_set_coarse_end_time(struct cmp_entity *ent, uint32_t coarse_time); int cmp_ent_set_fine_end_time(struct cmp_entity *ent, uint16_t fine_time); int cmp_ent_set_data_type(struct cmp_entity *ent, - enum cmp_ent_data_type data_type); -int cmp_ent_data_type_valid(enum cmp_ent_data_type data_type); + enum cmp_data_type data_type, int raw_mode); int cmp_ent_set_cmp_mode(struct cmp_entity *ent, uint32_t cmp_mode_used); int cmp_ent_set_model_value(struct cmp_entity *ent, uint32_t model_value_used); int cmp_ent_set_model_id(struct cmp_entity *ent, uint32_t model_id); @@ -247,7 +228,7 @@ uint64_t cmp_ent_get_end_timestamp(struct cmp_entity *ent); uint32_t cmp_ent_get_coarse_end_time(struct cmp_entity *ent); uint16_t cmp_ent_get_fine_end_time(struct cmp_entity *ent); -enum cmp_ent_data_type cmp_ent_get_data_type(struct cmp_entity *ent); +enum cmp_data_type cmp_ent_get_data_type(struct cmp_entity *ent); int cmp_ent_get_data_type_raw_bit(struct cmp_entity *ent); uint8_t cmp_ent_get_cmp_mode(struct cmp_entity *ent); uint8_t cmp_ent_get_model_value_used(struct cmp_entity *ent); @@ -301,7 +282,7 @@ ssize_t cmp_ent_get_cmp_data(struct cmp_entity *ent, uint32_t *data_buf, size_t data_buf_size); /* calculate the size of the compression entity header */ -uint32_t cmp_ent_cal_hdr_size(enum cmp_ent_data_type data_type); +uint32_t cmp_ent_cal_hdr_size(enum cmp_data_type data_type, int raw_mode); #if __has_include(<time.h>) diff --git a/include/cmp_guess.h b/include/cmp_guess.h index fe2ed09..9d3ac42 100644 --- a/include/cmp_guess.h +++ b/include/cmp_guess.h @@ -22,17 +22,23 @@ #include "cmp_support.h" + + #define DEFAULT_GUESS_LEVEL 2 -#define CMP_GUESS_DEF_MODE_DIFF MODE_DIFF_ZERO -#define CMP_GUESS_DEF_MODE_MODEL MODE_MODEL_MULTI +#define CMP_GUESS_DEF_MODE_DIFF CMP_MODE_DIFF_ZERO +#define CMP_GUESS_DEF_MODE_MODEL CMP_MODE_MODEL_MULTI +/* good guess for the spill parameter using the MODE_DIFF_MULTI */ +#define CMP_GOOD_SPILL_DIFF_MULTI 2U /* how often the model is updated before it is reset default value */ #define CMP_GUESS_N_MODEL_UPDATE_DEF 8 uint32_t cmp_guess(struct cmp_cfg *cfg, int level); void cmp_guess_set_model_updates(int n_model_updates); +uint32_t cmp_rdcu_get_good_spill(unsigned int golomb_par, enum cmp_mode cmp_mode); + uint16_t cmp_guess_model_value(int n_model_updates); #endif /* CMP_GUESS_H */ diff --git a/include/cmp_icu.h b/include/cmp_icu.h index cd7f193..81a1160 100644 --- a/include/cmp_icu.h +++ b/include/cmp_icu.h @@ -13,7 +13,6 @@ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * - * * @brief software compression library * @see Data Compression User Manual PLATO-UVIE-PL-UM-0001 */ @@ -21,13 +20,45 @@ #ifndef _CMP_ICU_H_ #define _CMP_ICU_H_ -#include "../include/cmp_support.h" +#include "cmp_support.h" + + +/* return code if the bitstream buffer is too small to store the whole bitstream */ +#define CMP_ERROR_SAMLL_BUF -2 + +/* return code if the value or the model is bigger than the max_used_bits + * parameter allows + */ +#define CMP_ERROR_HIGH_VALUE -3 + + +/* create and setup a compression configuration */ +struct cmp_cfg cmp_cfg_icu_create(enum cmp_data_type data_type, enum cmp_mode cmp_mode, + uint32_t model_value, uint32_t lossy_par); + +size_t cmp_cfg_icu_buffers(struct cmp_cfg *cfg, void *data_to_compress, + uint32_t data_samples, void *model_of_data, + void *updated_model, uint32_t *compressed_data, + uint32_t compressed_data_len_samples); + +int cmp_cfg_icu_imagette(struct cmp_cfg *cfg, uint32_t cmp_par, + uint32_t spillover_par); + +int cmp_cfg_fx_cob(struct cmp_cfg *cfg, + uint32_t cmp_par_exp_flags, uint32_t spillover_exp_flags, + uint32_t cmp_par_fx, uint32_t spillover_fx, + uint32_t cmp_par_ncob, uint32_t spillover_ncob, + uint32_t cmp_par_efx, uint32_t spillover_efx, + uint32_t cmp_par_ecob, uint32_t spillover_ecob, + uint32_t cmp_par_fx_cob_variance, uint32_t spillover_fx_cob_variance); -int icu_compress_data(struct cmp_cfg *cfg, struct cmp_info *info); +int cmp_cfg_aux(struct cmp_cfg *cfg, + uint32_t cmp_par_mean, uint32_t spillover_mean, + uint32_t cmp_par_variance, uint32_t spillover_variance, + uint32_t cmp_par_pixels_error, uint32_t spillover_pixels_error); -int cmp_pre_process(struct cmp_cfg *cfg); -int cmp_map_to_pos(struct cmp_cfg *cfg); -uint32_t cmp_encode_data(struct cmp_cfg *cfg); +/* start the compression */ +int icu_compress_data(const struct cmp_cfg *cfg); #endif /* _CMP_ICU_H_ */ diff --git a/include/cmp_io.h b/include/cmp_io.h index 39cfe59..f807825 100644 --- a/include/cmp_io.h +++ b/include/cmp_io.h @@ -31,8 +31,10 @@ void print_help(const char *program_name); int read_cmp_cfg(const char *file_name, struct cmp_cfg *cfg, int verbose_en); int read_cmp_info(const char *file_name, struct cmp_info *info, int verbose_en); -ssize_t read_file_data(const char *file_name, unsigned int cmp_mode, void *buf, - uint32_t buf_size, int verbose_en); +ssize_t read_file8(const char *file_name, uint8_t *buf, uint32_t buf_size, + int verbose_en); +ssize_t read_file_data(const char *file_name, enum cmp_data_type data_type, + void *buf, uint32_t buf_size, int verbose_en); ssize_t read_file_cmp_entity(const char *file_name, struct cmp_entity *ent, uint32_t ent_size, int verbose_en); ssize_t read_file32(const char *file_name, uint32_t *buf, uint32_t buf_size, @@ -42,8 +44,8 @@ uint32_t cmp_tool_gen_version_id(const char *version); int write_cmp_data_file(const void *buf, uint32_t buf_size, const char *output_prefix, const char *name_extension, int verbose); -int write_to_file16(const uint16_t *buf, uint32_t buf_len, const char - *output_prefix, const char *name_extension, int verbose); +int write_input_data_to_file(void *data, uint32_t data_size, enum cmp_data_type data_type, + const char *output_prefix, const char *name_extension, int verbose); int write_info(const struct cmp_info *info, const char *output_prefix, int rdcu_cfg); int write_cfg(const struct cmp_cfg *cfg, const char *output_prefix, int rdcu_cfg, diff --git a/include/cmp_rdcu.h b/include/cmp_rdcu.h index ee5cffb..14e0af9 100644 --- a/include/cmp_rdcu.h +++ b/include/cmp_rdcu.h @@ -21,10 +21,34 @@ #ifndef _CMP_RDCU_H_ #define _CMP_RDCU_H_ -#include <stdint.h> -#include "../include/cmp_support.h" +#include "cmp_support.h" +/* Compression Error Register bits definition, see RDCU-FRS-FN-0952 */ +#define SMALL_BUFFER_ERR_BIT 0x00 /* The length for the compressed data buffer is too small */ +#define CMP_MODE_ERR_BIT 0x01 /* The cmp_mode parameter is not set correctly */ +#define MODEL_VALUE_ERR_BIT 0x02 /* The model_value parameter is not set correctly */ +#define CMP_PAR_ERR_BIT 0x03 /* The spill, golomb_par combination is not set correctly */ +#define AP1_CMP_PAR_ERR_BIT 0x04 /* The ap1_spill, ap1_golomb_par combination is not set correctly (only HW compression) */ +#define AP2_CMP_PAR_ERR_BIT 0x05 /* The ap2_spill, ap2_golomb_par combination is not set correctly (only HW compression) */ +#define MB_ERR_BIT 0x06 /* Multi bit error detected by the memory controller (only HW compression) */ +#define SLAVE_BUSY_ERR_BIT 0x07 /* The bus master has received the "slave busy" status (only HW compression) */ + + +struct cmp_cfg rdcu_cfg_create(enum cmp_data_type data_type, enum cmp_mode cmp_mode, + uint32_t model_value, uint32_t lossy_par); +int rdcu_cfg_buffers(struct cmp_cfg *cfg, uint16_t *data_to_compress, + uint32_t data_samples, uint16_t *model_of_data, + uint32_t rdcu_data_adr, uint32_t rdcu_model_adr, + uint32_t rdcu_new_model_adr, uint32_t rdcu_buffer_adr, + uint32_t rdcu_buffer_lenght); +int rdcu_cfg_imagette(struct cmp_cfg *cfg, + uint32_t golomb_par, uint32_t spillover_par, + uint32_t ap1_golomb_par, uint32_t ap1_spillover_par, + uint32_t ap2_golomb_par, uint32_t ap2_spillover_par); + +int rdcu_cmp_cfg_is_invalid(const struct cmp_cfg *cfg); + int rdcu_compress_data(const struct cmp_cfg *cfg); int rdcu_read_cmp_status(struct cmp_status *status); diff --git a/include/cmp_support.h b/include/cmp_support.h index cfe4bbd..850f0a7 100644 --- a/include/cmp_support.h +++ b/include/cmp_support.h @@ -23,97 +23,165 @@ #include <stddef.h> -#define GOLOMB_PAR_EXPOSURE_FLAGS 1 - -/* Compression Error Register bits definition, see RDCU-FRS-FN-0952 */ -#define SMALL_BUFFER_ERR_BIT 0x00 /* The length for the compressed data buffer is too small */ -#define CMP_MODE_ERR_BIT 0x01 /* The cmp_mode parameter is not set correctly */ -#define MODEL_VALUE_ERR_BIT 0x02 /* The model_value parameter is not set correctly */ -#define CMP_PAR_ERR_BIT 0x03 /* The spill, golomb_par combination is not set correctly */ -#define AP1_CMP_PAR_ERR_BIT 0x04 /* The ap1_spill, ap1_golomb_par combination is not set correctly (only HW compression) */ -#define AP2_CMP_PAR_ERR_BIT 0x05 /* The ap2_spill, ap2_golomb_par combination is not set correctly (only HW compression) */ -#define MB_ERR_BIT 0x06 /* Multi bit error detected by the memory controller (only HW compression) */ -#define SLAVE_BUSY_ERR_BIT 0x07 /* The bus master has received the "slave busy" status (only HW compression) */ - -#define MODE_RAW 0 -#define MODE_MODEL_ZERO 1 -#define MODE_DIFF_ZERO 2 -#define MODE_MODEL_MULTI 3 -#define MODE_DIFF_MULTI 4 +#define CMP_LOSSLESS 0 +#define CMP_PAR_UNUNSED 0 + #define MAX_MODEL_VALUE \ - 16UL /* the maximal model values used in the update equation for the new model */ + 16U /* the maximal model values used in the update equation for the new model */ /* valid compression parameter ranges for RDCU compression according to PLATO-UVIE-PL-UM-0001 */ -#define MAX_RDCU_CMP_MODE 4UL -#define MIN_RDCU_GOLOMB_PAR 1UL -#define MAX_RDCU_GOLOMB_PAR 63UL -#define MIN_RDCU_SPILL 2UL -#define MAX_RDCU_ROUND 2UL +#define MAX_RDCU_CMP_MODE 4U +#define MIN_RDCU_GOLOMB_PAR 1U +#define MAX_RDCU_GOLOMB_PAR 63U +#define MIN_RDCU_SPILL 2U +#define MAX_RDCU_ROUND 2U /* for maximum spill value look at get_max_spill function */ /* valid compression parameter ranges for ICU compression */ -#define MIN_ICU_GOLOMB_PAR 1UL -#define MAX_ICU_GOLOMB_PAR UINT32_MAX -#define MIN_ICU_SPILL 2UL +#define MIN_ICU_GOLOMB_PAR 1U +#define MAX_ICU_GOLOMB_PAR 0x80000000U +#define MIN_ICU_SPILL 2U /* for maximum spill value look at get_max_spill function */ -#define MAX_ICU_ROUND 3UL +#define MAX_ICU_ROUND 3U +#define MAX_STUFF_CMP_PAR 32U + + +/* default imagette RDCU compression parameters for model compression */ +#define CMP_DEF_IMA_MODEL_DATA_TYPE DATA_TYPE_IMAGETTE +#define CMP_DEF_IMA_MODEL_CMP_MODE CMP_MODE_MODEL_MULTI +#define CMP_DEF_IMA_MODEL_MODEL_VALUE 8 +#define CMP_DEF_IMA_MODEL_LOSSY_PAR 0 + +#define CMP_DEF_IMA_MODEL_GOLOMB_PAR 4 +#define CMP_DEF_IMA_MODEL_SPILL_PAR 48 +#define CMP_DEF_IMA_MODEL_AP1_GOLOMB_PAR 3 +#define CMP_DEF_IMA_MODEL_AP1_SPILL_PAR 35 +#define CMP_DEF_IMA_MODEL_AP2_GOLOMB_PAR 5 +#define CMP_DEF_IMA_MODEL_AP2_SPILL_PAR 60 + +#define CMP_DEF_IMA_MODEL_RDCU_DATA_ADR 0x000000 +#define CMP_DEF_IMA_MODEL_RDCU_MODEL_ADR 0x200000 +#define CMP_DEF_IMA_MODEL_RDCU_UP_MODEL_ADR 0x400000 +#define CMP_DEF_IMA_MODEL_RDCU_BUFFER_ADR 0x600000 + +/* default imagette RDCU compression parameters for 1d-differencing compression */ +#define CMP_DEF_IMA_DIFF_DATA_TYPE DATA_TYPE_IMAGETTE +#define CMP_DEF_IMA_DIFF_CMP_MODE CMP_MODE_DIFF_ZERO +#define CMP_DEF_IMA_DIFF_MODEL_VALUE 8 /* not needed for 1d-differencing cmp_mode */ +#define CMP_DEF_IMA_DIFF_LOSSY_PAR 0 + +#define CMP_DEF_IMA_DIFF_GOLOMB_PAR 7 +#define CMP_DEF_IMA_DIFF_SPILL_PAR 60 +#define CMP_DEF_IMA_DIFF_AP1_GOLOMB_PAR 6 +#define CMP_DEF_IMA_DIFF_AP1_SPILL_PAR 48 +#define CMP_DEF_IMA_DIFF_AP2_GOLOMB_PAR 8 +#define CMP_DEF_IMA_DIFF_AP2_SPILL_PAR 72 + +#define CMP_DEF_IMA_DIFF_RDCU_DATA_ADR 0x000000 +#define CMP_DEF_IMA_DIFF_RDCU_MODEL_ADR 0x000000 /* not needed for 1d-differencing cmp_mode */ +#define CMP_DEF_IMA_DIFF_RDCU_UP_MODEL_ADR 0x000000 /* not needed for 1d-differencing cmp_mode */ +#define CMP_DEF_IMA_DIFF_RDCU_BUFFER_ADR 0x600000 + + +/* defined compression data product types */ +enum cmp_data_type { + DATA_TYPE_UNKOWN, + DATA_TYPE_IMAGETTE, + DATA_TYPE_IMAGETTE_ADAPTIVE, + DATA_TYPE_SAT_IMAGETTE, + DATA_TYPE_SAT_IMAGETTE_ADAPTIVE, + DATA_TYPE_OFFSET, + DATA_TYPE_BACKGROUND, + DATA_TYPE_SMEARING, + DATA_TYPE_S_FX, + DATA_TYPE_S_FX_DFX, + DATA_TYPE_S_FX_NCOB, + DATA_TYPE_S_FX_DFX_NCOB_ECOB, + DATA_TYPE_L_FX, + DATA_TYPE_L_FX_DFX, + DATA_TYPE_L_FX_NCOB, + DATA_TYPE_L_FX_DFX_NCOB_ECOB, + DATA_TYPE_F_FX, + DATA_TYPE_F_FX_DFX, + DATA_TYPE_F_FX_NCOB, + DATA_TYPE_F_FX_DFX_NCOB_ECOB, + DATA_TYPE_F_CAM_IMAGETTE, + DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE, + DATA_TYPE_F_CAM_OFFSET, + DATA_TYPE_F_CAM_BACKGROUND, +}; -#define SAM2BYT \ - 2 /* sample to byte conversion factor; one samples has 16 bits (2 bytes) */ -#define CMP_GOOD_SPILL_DIFF_MULTI 2 /* good guess for the spill parameter using the MODE_DIFF_MULTI */ +/* defined compression mode */ +enum cmp_mode { + CMP_MODE_RAW, + CMP_MODE_MODEL_ZERO, + CMP_MODE_DIFF_ZERO, + CMP_MODE_MODEL_MULTI, + CMP_MODE_DIFF_MULTI, + CMP_MODE_STUFF +}; + /** * @brief The cmp_cfg structure can contain the complete configuration of the HW as * well as the SW compressor. - * @note when using the 1d-differentiating mode or the raw mode (cmp_error = - * 0,2,4), the model parameters (model_value, model_buf, rdcu_model_adr, - * rdcu_new_model_adr) are ignored * @note the icu_output_buf will not be used for HW compression * @note the rdcu_***_adr parameters are ignored for SW compression - * @note semi adaptive compression not supported for SW compression; - * configuration parameters ap1\_golomb\_par, ap2\_golomb\_par, ap1\_spill, - * ap2\_spill will be ignored; */ struct cmp_cfg { + void *input_buf; /* Pointer to the data to compress buffer */ + void *model_buf; /* Pointer to the model buffer */ + void *icu_new_model_buf; /* Pointer to the updated model buffer (not used for RDCU compression )*/ + uint32_t *icu_output_buf; /* Pointer to the compressed data buffer (not used for RDCU compression) */ + uint32_t samples; /* Number of samples to compress, length of the data and model buffer + * (including the multi entity header by non-imagette data) + */ + uint32_t buffer_length; /* Length of the compressed data buffer in number of samples */ + uint32_t rdcu_data_adr; /* RDCU data to compress start address, the first data address in the RDCU SRAM; HW only */ + uint32_t rdcu_model_adr; /* RDCU model start address, the first model address in the RDCU SRAM */ + uint32_t rdcu_new_model_adr;/* RDCU updated model start address, the address in the RDCU SRAM where the updated model is stored */ + uint32_t rdcu_buffer_adr; /* RDCU compressed data start address, the first output data address in the RDCU SRAM */ + enum cmp_data_type data_type; /* Compression Data Product Types */ uint32_t cmp_mode; /* 0: raw mode * 1: model mode with zero escape symbol mechanism * 2: 1d differencing mode without input model with zero escape symbol mechanism * 3: model mode with multi escape symbol mechanism * 4: 1d differencing mode without input model multi escape symbol mechanism */ - uint32_t golomb_par; /* Golomb parameter for dictionary selection */ - uint32_t spill; /* Spillover threshold for encoding outliers */ uint32_t model_value; /* Model weighting parameter */ - uint32_t round; /* Number of noise bits to be rounded */ - uint32_t ap1_golomb_par; /* Adaptive 1 spillover threshold; HW only */ - uint32_t ap1_spill; /* Adaptive 1 Golomb parameter; HW only */ - uint32_t ap2_golomb_par; /* Adaptive 2 spillover threshold; HW only */ + uint32_t round; /* lossy compression parameter */ + uint32_t golomb_par; /* Golomb parameter for imagette data compression */ + uint32_t spill; /* Spillover threshold parameter for imagette compression */ + uint32_t ap1_golomb_par; /* Adaptive 1 spillover threshold for imagette data; HW only */ + uint32_t ap1_spill; /* Adaptive 1 Golomb parameter for imagette data; HW only */ + uint32_t ap2_golomb_par; /* Adaptive 2 spillover threshold for imagette data; HW only */ uint32_t ap2_spill; /* Adaptive 2 Golomb parameter; HW only */ - void *input_buf; /* Pointer to the data to compress buffer */ - uint32_t rdcu_data_adr; /* RDCU data to compress start address, the first data address in the RDCU SRAM; HW only */ - void *model_buf; /* Pointer to the model buffer */ - uint32_t rdcu_model_adr; /* RDCU model start address, the first model address in the RDCU SRAM */ - void *icu_new_model_buf; /* Pointer to the updated model buffer */ - uint32_t rdcu_new_model_adr;/* RDCU updated model start address, the address in the RDCU SRAM where the updated model is stored*/ - uint32_t samples; /* Number of samples (16 bit value) to compress, length of the data and model buffer */ - uint32_t *icu_output_buf; /* Pointer to the compressed data buffer (not used for RDCU compression) */ - uint32_t rdcu_buffer_adr; /* RDCU compressed data start address, the first output data address in the RDCU SRAM */ - uint32_t buffer_length; /* Length of the compressed data buffer in number of samples (16 bit values)*/ + uint32_t cmp_par_exp_flags; /* Compression parameter for exposure flags compression */ + uint32_t spill_exp_flags; /* Spillover threshold parameter for exposure flags compression */ + uint32_t cmp_par_fx; /* Compression parameter for normal flux compression */ + uint32_t spill_fx; /* Spillover threshold parameter for normal flux compression */ + uint32_t cmp_par_ncob; /* Compression parameter for normal center of brightness compression */ + uint32_t spill_ncob; /* Spillover threshold parameter for normal center of brightness compression */ + uint32_t cmp_par_efx; /* Compression parameter for extended flux compression */ + uint32_t spill_efx; /* Spillover threshold parameter for extended flux compression */ + uint32_t cmp_par_ecob; /* Compression parameter for executed center of brightness compression */ + uint32_t spill_ecob; /* Spillover threshold parameter for executed center of brightness compression */ + uint32_t cmp_par_fx_cob_variance; /* Compression parameter for flux/COB variance compression */ + uint32_t spill_fx_cob_variance; /* Spillover threshold parameter for flux/COB variance compression */ + uint32_t cmp_par_mean; /* Compression parameter for auxiliary science mean compression */ + uint32_t spill_mean; /* Spillover threshold parameter for auxiliary science mean compression */ + uint32_t cmp_par_variance; /* Compression parameter for auxiliary science variance compression */ + uint32_t spill_variance; /* Spillover threshold parameter for auxiliary science variance compression */ + uint32_t cmp_par_pixels_error; /* Compression parameter for auxiliary science outlier pixels number compression */ + uint32_t spill_pixels_error; /* Spillover threshold parameter for auxiliary science outlier pixels number compression */ }; -extern const struct cmp_cfg DEFAULT_CFG_MODEL; - -extern const struct cmp_cfg DEFAULT_CFG_DIFF; - - -/** - * @brief The cmp_status structure can contain the information of the - * compressor status register from the RDCU, see RDCU-FRS-FN-0632, - * but can also be used for the SW compression. +/* The cmp_status structure can contain the information of the compressor status + * register from the RDCU, see RDCU-FRS-FN-0632. */ struct cmp_status { @@ -125,18 +193,12 @@ struct cmp_status { }; -/** - * @brief The cmp_info structure can contain the information and metadata of an - * executed compression of the HW as well as the SW compressor. - * - * @note if SW compression is used the parameters rdcu_model_adr_used, rdcu_cmp_adr_used, - * ap1_cmp_size, ap2_cmp_size are not used and are therefore set to zero +/* The cmp_info structure can contain the information and metadata of an + * executed RDCU compression. */ struct cmp_info { uint32_t cmp_mode_used; /* Compression mode used */ - uint8_t model_value_used; /* Model weighting parameter used */ - uint8_t round_used; /* Number of noise bits to be rounded used */ uint32_t spill_used; /* Spillover threshold used */ uint32_t golomb_par_used; /* Golomb parameter used */ uint32_t samples_used; /* Number of samples (16 bit value) to be stored */ @@ -145,6 +207,8 @@ struct cmp_info { uint32_t ap2_cmp_size; /* Adaptive compressed data size 2; measured in bits */ uint32_t rdcu_new_model_adr_used; /* Updated model start address used */ uint32_t rdcu_cmp_adr_used; /* Compressed data start address */ + uint8_t model_value_used; /* Model weighting parameter used */ + uint8_t round_used; /* Number of noise bits to be rounded used */ uint16_t cmp_err; /* Compressor errors * [bit 0] small_buffer_err; The length for the compressed data buffer is too small * [bit 1] cmp_mode_err; The cmp_mode parameter is not set correctly @@ -155,33 +219,43 @@ struct cmp_info { * [bit 6] mb_err; Multi bit error detected by the memory controller (only HW compression) * [bit 7] slave_busy_err; The bus master has received the "slave busy" status (only HW compression) * [bit 8] slave_blocked_err; The bus master has received the “slave blocked” status (only HW compression) - * [bit 9] invalid address_err; The bus master has received the “invalid address” status (only HW compression) */ + * [bit 9] invalid address_err; The bus master has received the “invalid address” status (only HW compression) + */ }; + int is_a_pow_of_2(unsigned int v); int ilog_2(uint32_t x); -int model_mode_is_used(unsigned int cmp_mode); -int diff_mode_is_used(unsigned int cmp_mode); -int raw_mode_is_used(unsigned int cmp_mode); -int rdcu_supported_mode_is_used(unsigned int cmp_mode); -int cmp_mode_available(unsigned int cmp_mode); +unsigned int cmp_bit_to_4byte(unsigned int cmp_size_bit); + +int cmp_cfg_is_valid(const struct cmp_cfg *cfg); +int cmp_cfg_icu_gen_par_is_valid(const struct cmp_cfg *cfg); +int cmp_cfg_icu_buffers_is_valid(const struct cmp_cfg *cfg); +int cmp_cfg_imagette_is_valid(const struct cmp_cfg *cfg); +int cmp_cfg_fx_cob_is_valid(const struct cmp_cfg *cfg); +int cmp_cfg_aux_is_valid(const struct cmp_cfg *cfg); +uint32_t get_max_spill(unsigned int golomb_par, enum cmp_data_type); + +int cmp_data_type_valid(enum cmp_data_type data_type); +int rdcu_supported_data_type_is_used(enum cmp_data_type data_type); +int cmp_imagette_data_type_is_used(enum cmp_data_type data_type); +int cmp_ap_imagette_data_type_is_used(enum cmp_data_type data_type); +int cmp_fx_cob_data_type_is_used(enum cmp_data_type data_type); +int cmp_aux_data_type_is_used(enum cmp_data_type data_type); -int zero_escape_mech_is_used(unsigned int cmp_mode); -int multi_escape_mech_is_used(unsigned int cmp_mode); +int cmp_mode_is_supported(enum cmp_mode cmp_mode); +int model_mode_is_used(enum cmp_mode cmp_mode); +int diff_mode_is_used(enum cmp_mode cmp_mode); +int raw_mode_is_used(enum cmp_mode cmp_mode); +int rdcu_supported_cmp_mode_is_used(enum cmp_mode cmp_mode); +int zero_escape_mech_is_used(enum cmp_mode cmp_mode); +int multi_escape_mech_is_used(enum cmp_mode cmp_mode); unsigned int round_fwd(unsigned int value, unsigned int round); unsigned int round_inv(unsigned int value, unsigned int round); -unsigned int cal_up_model(unsigned int data, unsigned int model, unsigned int - model_value); - -uint32_t get_max_spill(unsigned int golomb_par, unsigned int cmp_mode); -uint32_t cmp_get_good_spill(unsigned int golomb_par, unsigned int cmp_mode); - -size_t size_of_a_sample(unsigned int cmp_mode); -int cmp_input_size_to_samples(unsigned int size, unsigned int cmp_mode); -unsigned int cmp_bit_to_4byte(unsigned int cmp_size_bit); -unsigned int cmp_cal_size_of_data(unsigned int samples, unsigned int cmp_mode); +unsigned int cmp_up_model(unsigned int data, unsigned int model, + unsigned int model_value, unsigned int round); void print_cmp_cfg(const struct cmp_cfg *cfg); diff --git a/include/decmp.h b/include/decmp.h index 139273f..2f8478c 100644 --- a/include/decmp.h +++ b/include/decmp.h @@ -19,13 +19,17 @@ #ifndef DECMP_H_ #define DECMP_H_ -#include "../include/cmp_support.h" +#include "cmp_entity.h" +#include "cmp_support.h" void *malloc_decompressed_data(const struct cmp_info *info); -int decompress_data(const void *compressed_data, void *de_model_buf, +int decompress_data(uint32_t *compressed_data, void *de_model_buf, const struct cmp_info *info, void *decompressed_data); +int decompress_cmp_entiy(struct cmp_entity *ent, void *model_buf, + void *up_model_buf, void *decompressed_data); + double get_compression_ratio(const struct cmp_info *info); #endif /* DECMP_H_ */ diff --git a/lib/cmp_data_types.c b/lib/cmp_data_types.c index cf8c08c..749964d 100644 --- a/lib/cmp_data_types.c +++ b/lib/cmp_data_types.c @@ -17,770 +17,504 @@ */ -#include "../include/cmp_data_types.h" -#include "../include/cmp_support.h" -#include "../include/cmp_debug.h" -#include "../include/byteorder.h" +#include "cmp_data_types.h" +#include "cmp_debug.h" +#include "byteorder.h" + + +/* the maximum length of the different data products types in bits */ +struct cmp_max_used_bits max_used_bits = { + 0, /* default version */ + MAX_USED_S_FX_EXPOSURE_FLAGS_BITS, /* s_fx_exp_flags */ + MAX_USED_S_FX_BITS, /* s_fx */ + MAX_USED_S_EFX_BITS, /* s_efx */ + MAX_USED_S_NCOB_BITS, /* s_ncob_x and s_ncob_y */ + MAX_USED_S_ECOB_BITS, /* s_ecob_x and s_ncob_y */ + MAX_USED_F_FX_BITS, /* f_fx */ + MAX_USED_F_EFX_BITS, /* f_efx */ + MAX_USED_F_NCOB_BITS, /* f_ncob_x and f_ncob_y */ + MAX_USED_F_ECOB_BITS, /* f_ecob_x and f_ncob_y */ + MAX_USED_L_FX_EXPOSURE_FLAGS_BITS, /* l_fx_exp_flags */ + MAX_USED_L_FX_BITS, /* l_fx */ + MAX_USED_L_FX_VARIANCE_BITS, /* l_fx_variance */ + MAX_USED_L_EFX_BITS, /* l_efx */ + MAX_USED_L_NCOB_BITS, /* l_ncob_x and l_ncob_y */ + MAX_USED_L_ECOB_BITS, /* l_ecob_x and l_ncob_y */ + MAX_USED_L_COB_VARIANCE_BITS, /* l_cob_x_variance and l_cob_y_variance */ + MAX_USED_NC_IMAGETTE_BITS, /* nc_imagette */ + MAX_USED_SATURATED_IMAGETTE_BITS, /* saturated_imagette */ + MAX_USED_NC_OFFSET_MEAN_BITS, /* nc_offset_mean */ + MAX_USED_NC_OFFSET_VARIANCE_BITS, /* nc_offset_variance */ + MAX_USED_NC_BACKGROUND_MEAN_BITS, /* nc_background_mean */ + MAX_USED_NC_BACKGROUND_VARIANCE_BITS, /* nc_background_variance */ + MAX_USED_NC_BACKGROUND_OUTLIER_PIXELS_BITS, /* nc_background_outlier_pixels */ + MAX_USED_SMEARING_MEAN_BITS, /* smeating_mean */ + MAX_USED_SMEARING_VARIANCE_MEAN_BITS, /* smeating_variance_mean */ + MAX_USED_SMEARING_OUTLIER_PIXELS_BITS, /* smearing_outlier_pixels */ + MAX_USED_FC_IMAGETTE_BITS, /* fc_imagette */ + MAX_USED_FC_OFFSET_MEAN_BITS, /* fc_offset_mean */ + MAX_USED_FC_OFFSET_VARIANCE_BITS, /* fc_offset_variance */ + MAX_USED_FC_OFFSET_PIXEL_IN_ERROR_BITS, /* fc_offset_pixel_in_error */ + MAX_USED_FC_BACKGROUND_MEAN_BITS, /* fc_background_mean */ + MAX_USED_FC_BACKGROUND_VARIANCE_BITS, /* fc_background_variance */ + MAX_USED_FC_BACKGROUND_OUTLIER_PIXELS_BITS /* fc_background_outlier_pixels */ +}; /** - * @brief rounding down the least significant digits of a uint16_t data buffer + * @brief sets the maximum length of the different data products types * - * @note this step involves data loss (if round > 0) - * @note change the data buffer in-place - * - * @param data_buf uint16_t formatted data buffer - * @param samples the size of the data buffer measured in uint16_t samples - * @param round number of bits to round; if zero no rounding takes place - * - * @returns 0 on success, error otherwise + * @param set_max_used_bits pointer to a structure with the maximum length + * of the different data products types in bits */ -int lossy_rounding_16(uint16_t *data_buf, unsigned int samples, unsigned int - round) +void cmp_set_max_used_bits(const struct cmp_max_used_bits *set_max_used_bits) { - size_t i; - - if (!samples) - return 0; - - if (!data_buf) - return -1; - - /* round 0 means loss less compression, no further processing is - * necessary */ - if (round == 0) - return 0; - - for (i = 0; i < samples; i++) - data_buf[i] = round_fwd(data_buf[i], round); /* this is the lossy step */ - - return 0; + if (set_max_used_bits) + max_used_bits = *set_max_used_bits; } /** - * @brief rounding back the least significant digits of the data buffer - * - * @param data_buf pointer to the data to process - * @param samples_used the size of the data and model buffer in 16 bit units - * @param round_used used number of bits to round; if zero no rounding takes place + * @brief get the maximum length of the different data products types * - * @returns 0 on success, error otherwise + * @returns a structure with the used maximum length of the different data + * products types in bits */ -int de_lossy_rounding_16(uint16_t *data_buf, uint32_t samples_used, uint32_t - round_used) -{ - size_t i; - - if (!samples_used) - return 0; - - if (!data_buf) - return -1; - - /* round 0 means loss less compression, no further processing is necessary */ - if (round_used == 0) - return 0; - - for (i = 0; i < samples_used; i++) { - /* check if data are not to big for a overflow */ - uint16_t mask = (uint16_t)(~0U << (16-round_used)); - if (data_buf[i] & mask) { - debug_print("de_lossy_rounding_16 failed!\n"); - return -1; - } - data_buf[i] = round_inv(data_buf[i], round_used); - } - return 0; -} - - -static void be_to_cpus_16(uint16_t *a, uint32_t samples) +struct cmp_max_used_bits cmp_get_max_used_bits(void) { - size_t i; - - for (i = 0; i < samples; ++i) - be16_to_cpus(&a[i]); + return max_used_bits; } /** - * @brief rounding down the least significant digits of a uint32_t data buffer - * - * @note this step involves data loss (if round > 0) - * @note change the data buffer in-place + * @brief calculate the size of a sample for the different compression data type * - * @param data_buf a uint32_t formatted data buffer - * @param samples the size of the data buffer measured in uint16_t samples - * @param round number of bits to round; if zero no rounding takes place + * @param data_type compression data_type * - * @returns 0 on success, error otherwise + * @returns the size of a data sample in bytes for the selected compression + * data type; zero on unknown data type */ -int lossy_rounding_32(uint32_t *data_buf, unsigned int samples, unsigned int - round) +size_t size_of_a_sample(enum cmp_data_type data_type) { - size_t i; - - if (!samples) - return 0; - - if (!data_buf) - return -1; - - /* round 0 means loss less compression, no further processing is - * necessary */ - if (round == 0) - return 0; - - for (i = 0; i < samples; i++) - data_buf[i] = round_fwd(data_buf[i], round); /* this is the lossy step */ - - return 0; -} - - -int de_lossy_rounding_32(uint32_t *data_buf, uint32_t samples_used, uint32_t - round_used) -{ - size_t i; - - if (!samples_used) - return 0; - - if (!data_buf) - return -1; - - /* round 0 means loss less compression, no further processing is necessary */ - if (round_used == 0) - return 0; - - for (i = 0; i < samples_used; i++) { - /* check if data are not to big for a overflow */ - uint32_t mask = (uint32_t)(~0U << (32-round_used)); - if (data_buf[i] & mask) { - debug_print("de_lossy_rounding_32 failed!\n"); - return -1; - } - data_buf[i] = round_inv(data_buf[i], round_used); + size_t sample_size = 0; + + switch (data_type) { + case DATA_TYPE_IMAGETTE: + case DATA_TYPE_IMAGETTE_ADAPTIVE: + case DATA_TYPE_SAT_IMAGETTE: + case DATA_TYPE_SAT_IMAGETTE_ADAPTIVE: + sample_size = sizeof(uint16_t); + break; + case DATA_TYPE_OFFSET: + sample_size = sizeof(struct nc_offset); + break; + case DATA_TYPE_BACKGROUND: + sample_size = sizeof(struct nc_background); + break; + case DATA_TYPE_SMEARING: + sample_size = sizeof(struct smearing); + break; + case DATA_TYPE_S_FX: + sample_size = sizeof(struct s_fx); + break; + case DATA_TYPE_S_FX_DFX: + sample_size = sizeof(struct s_fx_efx); + break; + case DATA_TYPE_S_FX_NCOB: + sample_size = sizeof(struct s_fx_ncob); + break; + case DATA_TYPE_S_FX_DFX_NCOB_ECOB: + sample_size = sizeof(struct s_fx_efx_ncob_ecob); + break; + case DATA_TYPE_L_FX: + sample_size = sizeof(struct l_fx); + break; + case DATA_TYPE_L_FX_DFX: + sample_size = sizeof(struct l_fx_efx); + break; + case DATA_TYPE_L_FX_NCOB: + sample_size = sizeof(struct l_fx_ncob); + break; + case DATA_TYPE_L_FX_DFX_NCOB_ECOB: + sample_size = sizeof(struct l_fx_efx_ncob_ecob); + break; + case DATA_TYPE_F_FX: + sample_size = sizeof(struct f_fx); + break; + case DATA_TYPE_F_FX_DFX: + sample_size = sizeof(struct f_fx_efx); + break; + case DATA_TYPE_F_FX_NCOB: + sample_size = sizeof(struct f_fx_ncob); + break; + case DATA_TYPE_F_FX_DFX_NCOB_ECOB: + sample_size = sizeof(struct f_fx_efx_ncob_ecob); + break; + case DATA_TYPE_F_CAM_OFFSET: + case DATA_TYPE_F_CAM_BACKGROUND: + case DATA_TYPE_UNKOWN: + default: + debug_print("Error: Compression data type is not supported.\n"); + break; } - return 0; -} - - -struct S_FX sub_S_FX(struct S_FX a, struct S_FX b) -{ - struct S_FX result; - - result.EXPOSURE_FLAGS = a.EXPOSURE_FLAGS - b.EXPOSURE_FLAGS; - result.FX = a.FX - b.FX; - - return result; -} - - -struct S_FX add_S_FX(struct S_FX a, struct S_FX b) -{ - struct S_FX result; - - result.EXPOSURE_FLAGS = a.EXPOSURE_FLAGS + b.EXPOSURE_FLAGS; - result.FX = a.FX + b.FX; - - return result; + return sample_size; } /** - * @brief rounding down the least significant digits of a S_FX data buffer + * @brief calculate the need bytes for the data * - * @note this step involves data loss (if round > 0) - * @note change the data buffer in-place - * @note the exposure_flags are not rounded + * @param samples number of data samples + * @param data_type compression data_type * - * @param data_buf a S_FX formatted data buffer - * @param samples the size of the data buffer measured in S_FX samples - * @param round number of bits to round; if zero no rounding takes place + * @note for non-imagette data program types the multi entry header size is added * - * @returns 0 on success, error otherwise + * @returns the size in bytes to store the data sample */ -int lossy_rounding_S_FX(struct S_FX *data_buf, unsigned int samples, unsigned - int round) +unsigned int cmp_cal_size_of_data(unsigned int samples, enum cmp_data_type data_type) { - size_t i; + unsigned int s = size_of_a_sample(data_type); - if (!samples) + if (!s) return 0; - if (!data_buf) - return -1; + s *= samples; - /* round 0 means loss less compression, no further processing is - * necessary */ - if (round == 0) - return 0; - - for (i = 0; i < samples; i++) - data_buf[i].FX = round_fwd(data_buf[i].FX, round); + if (!rdcu_supported_data_type_is_used(data_type)) + s += MULTI_ENTRY_HDR_SIZE; - return 0; + return s; } -int de_lossy_rounding_S_FX(struct S_FX *data_buf, unsigned int samples_used, - unsigned int round_used) -{ - size_t i; +/** + * @brief calculates the number of samples for a given data size for the + * different compression modes + * + * @param size size of the data in bytes + * @param data_type compression data type + * + * @returns the number samples for the given compression mode; negative on error + */ - if (!samples_used) - return 0; +int cmp_input_size_to_samples(unsigned int size, enum cmp_data_type data_type) +{ + int samples_size = (int)size_of_a_sample(data_type); - if (!data_buf) + if (!samples_size) return -1; - if (round_used == 0) /* round 0 means loss less compression, no further processing is necessary */ - return 0; - - for (i = 0; i < samples_used; i++) { - uint32_t mask = (~0U << (32-round_used)); - - if (data_buf[i].FX & mask) { - debug_print("Errolr: de_lossy_rounding_S_FX failed\n"); + if (!rdcu_supported_data_type_is_used(data_type)) { + if (size < MULTI_ENTRY_HDR_SIZE) return -1; - } - - data_buf[i].FX = round_inv(data_buf[i].FX, round_used); + size -= MULTI_ENTRY_HDR_SIZE; } - return 0; -} - - -struct S_FX cal_up_model_S_FX(struct S_FX data_buf, struct S_FX model_buf, - unsigned int model_value) -{ - struct S_FX result; - result.EXPOSURE_FLAGS = (uint8_t)cal_up_model(data_buf.EXPOSURE_FLAGS, - model_buf.EXPOSURE_FLAGS, - model_value); - result.FX = cal_up_model(data_buf.FX, model_buf.FX, model_value); + if (size % samples_size) + return -1; - return result; + return size/samples_size; } -void be_to_cpus_S_FX(struct S_FX *a, uint32_t samples) +static uint32_t be24_to_cpu(uint32_t a) { - size_t i; - - for (i = 0; i < samples; ++i) - a[i].FX = be32_to_cpu(a[i].FX); + return be32_to_cpu(a) >> 8; } -struct S_FX_EFX sub_S_FX_EFX(struct S_FX_EFX a, struct S_FX_EFX b) +static void be_to_cpus_16(uint16_t *a, int samples) { - struct S_FX_EFX result; + int i; - result.EXPOSURE_FLAGS = a.EXPOSURE_FLAGS - b.EXPOSURE_FLAGS; - result.FX = a.FX - b.FX; - result.EFX = a.EFX - b.EFX; - - return result; + for (i = 0; i < samples; ++i) + be16_to_cpus(&a[i]); } -struct S_FX_EFX add_S_FX_EFX(struct S_FX_EFX a, struct S_FX_EFX b) +static void be_to_cpus_nc_offset(struct nc_offset *a, int samples) { - struct S_FX_EFX result; - - result.EXPOSURE_FLAGS = a.EXPOSURE_FLAGS + b.EXPOSURE_FLAGS; - result.FX = a.FX + b.FX; - result.EFX = a.EFX + b.EFX; + int i; - return result; + for (i = 0; i < samples; ++i) { + a[i].mean = be32_to_cpu(a[i].mean); + a[i].variance = be32_to_cpu(a[i].variance); + } } -/** - * @brief rounding down the least significant digits of a S_FX_EFX data buffer - * - * @note this step involves data loss (if round > 0) - * @note change the data buffer in-place - * @note the exposure_flags are not rounded - * - * @param data S_FX_EFX formatted data buffer - * @param samples the size of the data buffer measured in S_FX_EFX samples - * @param round number of bits to round; if zero no rounding takes place - * - * @returns 0 on success, error otherwise - */ - -int lossy_rounding_S_FX_EFX(struct S_FX_EFX *data, unsigned int samples, - unsigned int round) +static void be_to_cpus_nc_background(struct nc_background *a, int samples) { - size_t i; + int i; - if (!samples) - return 0; - - if (!data) - return -1; - - /* round 0 means loss less compression, no further processing is - * necessary */ - if (round == 0) - return 0; - - for (i = 0; i < samples; i++) { - data[i].FX = round_fwd(data[i].FX, round); - data[i].EFX = round_fwd(data[i].EFX, round); + for (i = 0; i < samples; ++i) { + a[i].mean = be32_to_cpu(a[i].mean); + a[i].variance = be32_to_cpu(a[i].variance); + a[i].outlier_pixels = be16_to_cpu(a[i].outlier_pixels); } - return 0; } -int de_lossy_rounding_S_FX_EFX(struct S_FX_EFX *data_buf, unsigned int - samples_used, unsigned int round_used) +static void be_to_cpus_smearing(struct smearing *a, int samples) { - size_t i; - - if (!samples_used) - return 0; - - if (!data_buf) - return -1; - - if (round_used == 0) /* round 0 means loss less compression, no further processing is necessary */ - return 0; - - for (i = 0; i < samples_used; i++) { - uint32_t mask = (~0U << (32-round_used)); - - if (data_buf[i].FX & mask) { - debug_print("de_lossy_rounding_S_FX failed!\n"); - return -1; - } - if (data_buf[i].EFX & mask) { - debug_print("de_lossy_rounding_S_FX failed!\n"); - return -1; - } + int i; - data_buf[i].FX = round_inv(data_buf[i].FX, round_used); - data_buf[i].EFX = round_inv(data_buf[i].EFX, round_used); + for (i = 0; i < samples; ++i) { + a[i].mean = be32_to_cpu(a[i].mean); + a[i].variance_mean = be16_to_cpu(a[i].variance_mean); + a[i].outlier_pixels = be16_to_cpu(a[i].outlier_pixels); } - return 0; } -struct S_FX_EFX cal_up_model_S_FX_EFX(struct S_FX_EFX data_buf, struct S_FX_EFX - model_buf, unsigned int model_value) +static void be_to_cpus_s_fx(struct s_fx *a, int samples) { - struct S_FX_EFX result; + int i; - result.EXPOSURE_FLAGS = - (uint8_t)cal_up_model(data_buf.EXPOSURE_FLAGS, - model_buf.EXPOSURE_FLAGS, model_value); - result.FX = cal_up_model(data_buf.FX, model_buf.FX, model_value); - result.EFX = cal_up_model(data_buf.EFX, model_buf.FX, model_value); - - return result; + for (i = 0; i < samples; ++i) + a[i].fx = be32_to_cpu(a[i].fx); } -void be_to_cpus_S_FX_EFX(struct S_FX_EFX *a, uint32_t samples) +static void be_to_cpus_s_fx_efx(struct s_fx_efx *a, int samples) { - size_t i; + int i; for (i = 0; i < samples; ++i) { - a[i].FX = be32_to_cpu(a[i].FX); - a[i].EFX = be32_to_cpu(a[i].EFX); + a[i].fx = be32_to_cpu(a[i].fx); + a[i].efx = be32_to_cpu(a[i].efx); } } -struct S_FX_NCOB sub_S_FX_NCOB(struct S_FX_NCOB a, struct S_FX_NCOB b) +static void be_to_cpus_s_fx_ncob(struct s_fx_ncob *a, int samples) { - struct S_FX_NCOB result; - - result.EXPOSURE_FLAGS = a.EXPOSURE_FLAGS - b.EXPOSURE_FLAGS; - result.FX = a.FX - b.FX; - result.NCOB_X = a.NCOB_X - b.NCOB_X; - result.NCOB_Y = a.NCOB_Y - b.NCOB_Y; - - return result; -} - + int i; -struct S_FX_NCOB add_S_FX_NCOB(struct S_FX_NCOB a, struct S_FX_NCOB b) -{ - struct S_FX_NCOB result; - - result.EXPOSURE_FLAGS = a.EXPOSURE_FLAGS + b.EXPOSURE_FLAGS; - result.FX = a.FX + b.FX; - result.NCOB_X = a.NCOB_X + b.NCOB_X; - result.NCOB_Y = a.NCOB_Y + b.NCOB_Y; - - return result; + for (i = 0; i < samples; ++i) { + a[i].fx = be32_to_cpu(a[i].fx); + a[i].ncob_x = be32_to_cpu(a[i].ncob_x); + a[i].ncob_y = be32_to_cpu(a[i].ncob_y); + } } -/** - * @brief rounding down the least significant digits of a S_FX_NCOB data buffer - * - * @note this step involves data loss (if round > 0) - * @note change the data buffer in-place - * @note the exposure_flags are not rounded - * - * @param data_buf a S_FX_NCOB formatted data buffer - * @param samples the size of the data buffer measured in S_FX_NCOB samples - * @param round number of bits to round; if zero no rounding takes place - * - * @returns 0 on success, error otherwise - */ - -int lossy_rounding_S_FX_NCOB(struct S_FX_NCOB *data_buf, unsigned int samples, - unsigned int round) +static void be_to_cpus_s_fx_efx_ncob_ecob(struct s_fx_efx_ncob_ecob *a, int samples) { - size_t i; - - if (!samples) - return 0; - - if (!data_buf) - return -1; + int i; - /* round 0 means loss less compression, no further processing is - * necessary */ - if (round == 0) - return 0; - - for (i = 0; i < samples; i++) { - data_buf[i].FX = round_fwd(data_buf[i].FX, round); - data_buf[i].NCOB_X = round_fwd(data_buf[i].NCOB_X, round); - data_buf[i].NCOB_Y = round_fwd(data_buf[i].NCOB_Y, round); + for (i = 0; i < samples; ++i) { + a[i].fx = be32_to_cpu(a[i].fx); + a[i].ncob_x = be32_to_cpu(a[i].ncob_x); + a[i].ncob_y = be32_to_cpu(a[i].ncob_y); + a[i].efx = be32_to_cpu(a[i].efx); + a[i].ecob_x = be32_to_cpu(a[i].ecob_x); + a[i].ecob_y = be32_to_cpu(a[i].ecob_y); } - return 0; } -int de_lossy_rounding_S_FX_NCOB(struct S_FX_NCOB *data_buf, unsigned int - samples_used, unsigned int round_used) +static void be_to_cpus_l_fx(struct l_fx *a, int samples) { - size_t i; - - if (!samples_used) - return 0; - - if (!data_buf) - return -1; - - if (round_used == 0) /* round 0 means loss less compression, no further processing is necessary */ - return 0; - - for (i = 0; i < samples_used; i++) { - uint32_t mask = (~0U << (32-round_used)); - - if (data_buf[i].FX & mask) { - debug_print("de_lossy_rounding_S_FX_NCOB failed!\n"); - return -1; - } - if (data_buf[i].NCOB_X & mask) { - debug_print("de_lossy_rounding_S_FX_NCOB failed!\n"); - return -1; - } - if (data_buf[i].NCOB_Y & mask) { - debug_print("de_lossy_rounding_S_FX_NCOB failed!\n"); - return -1; - } + int i; - data_buf[i].FX = round_inv(data_buf[i].FX, round_used); - data_buf[i].NCOB_X = round_inv(data_buf[i].NCOB_X, round_used); - data_buf[i].NCOB_Y = round_inv(data_buf[i].NCOB_Y, round_used); + for (i = 0; i < samples; ++i) { + a[i].exp_flags = be24_to_cpu(a[i].exp_flags); + a[i].fx = be32_to_cpu(a[i].fx); + a[i].fx_variance = be32_to_cpu(a[i].fx_variance); } - return 0; } -struct S_FX_NCOB cal_up_model_S_FX_NCOB(struct S_FX_NCOB data_buf, struct S_FX_NCOB - model_buf, unsigned int model_value) +static void be_to_cpus_l_fx_efx(struct l_fx_efx *a, int samples) { - struct S_FX_NCOB result; + int i; - result.EXPOSURE_FLAGS = - (uint8_t)cal_up_model(data_buf.EXPOSURE_FLAGS, - model_buf.EXPOSURE_FLAGS, model_value); - result.FX = cal_up_model(data_buf.FX, model_buf.FX, model_value); - result.NCOB_X = cal_up_model(data_buf.NCOB_X, model_buf.NCOB_X, model_value); - result.NCOB_Y = cal_up_model(data_buf.NCOB_Y, model_buf.NCOB_Y, model_value); - - return result; + for (i = 0; i < samples; ++i) { + a[i].exp_flags = be24_to_cpu(a[i].exp_flags); + a[i].fx = be32_to_cpu(a[i].fx); + a[i].efx = be32_to_cpu(a[i].efx); + a[i].fx_variance = be32_to_cpu(a[i].fx_variance); + } } -void be_to_cpus_S_FX_NCOB(struct S_FX_NCOB *a, uint32_t samples) +static void be_to_cpus_l_fx_ncob(struct l_fx_ncob *a, int samples) { - size_t i; + int i; for (i = 0; i < samples; ++i) { - a[i].FX = be32_to_cpu(a[i].FX); - a[i].NCOB_X = be32_to_cpu(a[i].NCOB_X); - a[i].NCOB_Y = be32_to_cpu(a[i].NCOB_Y); + a[i].exp_flags = be24_to_cpu(a[i].exp_flags); + a[i].fx = be32_to_cpu(a[i].fx); + a[i].ncob_x = be32_to_cpu(a[i].ncob_x); + a[i].ncob_y = be32_to_cpu(a[i].ncob_y); + a[i].fx_variance = be32_to_cpu(a[i].fx_variance); + a[i].cob_x_variance = be32_to_cpu(a[i].cob_x_variance); + a[i].cob_y_variance = be32_to_cpu(a[i].cob_y_variance); } } -struct S_FX_EFX_NCOB_ECOB sub_S_FX_EFX_NCOB_ECOB(struct S_FX_EFX_NCOB_ECOB a, - struct S_FX_EFX_NCOB_ECOB b) +static void be_to_cpus_l_fx_efx_ncob_ecob(struct l_fx_efx_ncob_ecob *a, int samples) { - struct S_FX_EFX_NCOB_ECOB result; - - result.EXPOSURE_FLAGS = a.EXPOSURE_FLAGS - b.EXPOSURE_FLAGS; - result.FX = a.FX - b.FX; - result.NCOB_X = a.NCOB_X - b.NCOB_X; - result.NCOB_Y = a.NCOB_Y - b.NCOB_Y; - result.EFX = a.EFX - b.EFX; - result.ECOB_X = a.ECOB_X - b.ECOB_X; - result.ECOB_Y = a.ECOB_Y - b.ECOB_Y; + int i; - return result; + for (i = 0; i < samples; ++i) { + a[i].exp_flags = be24_to_cpu(a[i].exp_flags); + a[i].fx = be32_to_cpu(a[i].fx); + a[i].ncob_x = be32_to_cpu(a[i].ncob_x); + a[i].ncob_y = be32_to_cpu(a[i].ncob_y); + a[i].efx = be32_to_cpu(a[i].efx); + a[i].ecob_x = be32_to_cpu(a[i].ecob_x); + a[i].ecob_y = be32_to_cpu(a[i].ecob_y); + a[i].fx_variance = be32_to_cpu(a[i].fx_variance); + a[i].cob_x_variance = be32_to_cpu(a[i].cob_x_variance); + a[i].cob_y_variance = be32_to_cpu(a[i].cob_y_variance); + } } -struct S_FX_EFX_NCOB_ECOB add_S_FX_EFX_NCOB_ECOB(struct S_FX_EFX_NCOB_ECOB a, - struct S_FX_EFX_NCOB_ECOB b) +static void be_to_cpus_f_fx(struct f_fx *a, int samples) { - struct S_FX_EFX_NCOB_ECOB result; - - result.EXPOSURE_FLAGS = a.EXPOSURE_FLAGS + b.EXPOSURE_FLAGS; - result.FX = a.FX + b.FX; - result.NCOB_X = a.NCOB_X + b.NCOB_X; - result.NCOB_Y = a.NCOB_Y + b.NCOB_Y; - result.EFX = a.EFX + b.EFX; - result.ECOB_X = a.ECOB_X + b.ECOB_X; - result.ECOB_Y = a.ECOB_Y + b.ECOB_Y; + int i; - return result; + for (i = 0; i < samples; ++i) + a[i].fx = be32_to_cpu(a[i].fx); } -/** - * @brief rounding down the least significant digits of a S_FX_EFX_NCOB_ECOB data - * buffer - * - * @note this step involves data loss (if round > 0) - * @note change the data buffer in-place - * @note the exposure_flags are not rounded - * - * @param data_buf a S_FX_EFX_NCOB_ECOB formatted data buffer - * @param samples the size of the data buffer measured in - * S_FX_EFX_NCOB_ECOB samples - * @param round number of bits to round; if zero no rounding takes place - * - * @returns 0 on success, error otherwise - */ - -int lossy_rounding_S_FX_EFX_NCOB_ECOB(struct S_FX_EFX_NCOB_ECOB *data_buf, - unsigned int samples, unsigned int round) +static void be_to_cpus_f_fx_efx(struct f_fx_efx *a, int samples) { - size_t i; - - if (!samples) - return 0; - - if (!data_buf) - return -1; - - if (round == 0) /* round 0 means loss less compression, no further processing is necessary */ - return 0; + int i; - for (i = 0; i < samples; i++) { - data_buf[i].FX = round_fwd(data_buf[i].FX, round); - data_buf[i].NCOB_X = round_fwd(data_buf[i].NCOB_X, round); - data_buf[i].NCOB_Y = round_fwd(data_buf[i].NCOB_Y, round); - data_buf[i].EFX = round_fwd(data_buf[i].EFX, round); - data_buf[i].ECOB_X = round_fwd(data_buf[i].ECOB_X, round); - data_buf[i].ECOB_Y = round_fwd(data_buf[i].ECOB_Y, round); + for (i = 0; i < samples; ++i) { + a[i].fx = be32_to_cpu(a[i].fx); + a[i].efx = be32_to_cpu(a[i].efx); } - return 0; } -int de_lossy_rounding_S_FX_EFX_NCOB_ECOB(struct S_FX_EFX_NCOB_ECOB *data_buf, - unsigned int samples_used, - unsigned int round_used) +static void be_to_cpus_f_fx_ncob(struct f_fx_ncob *a, int samples) { - size_t i; - - if (!samples_used) - return 0; - - if (!data_buf) - return -1; - - if (round_used == 0) /* round 0 means loss less compression, no further processing is necessary */ - return 0; + int i; - for (i = 0; i < samples_used; i++) { - uint32_t mask = (~0U << (32-round_used)); - - if (data_buf[i].FX & mask) { - debug_print("de_lossy_rounding_S_FX_EFX_NCOB_ECOB failed!\n"); - return -1; - } - if (data_buf[i].NCOB_X & mask) { - debug_print("de_lossy_rounding_S_FX_EFX_NCOB_ECOB failed!\n"); - return -1; - } - if (data_buf[i].NCOB_Y & mask) { - debug_print("de_lossy_rounding_S_FX_EFX_NCOB_ECOB failed!\n"); - return -1; - } - if (data_buf[i].EFX & mask) { - debug_print("de_lossy_rounding_S_FX_EFX_NCOB_ECOB failed!\n"); - return -1; - } - if (data_buf[i].ECOB_X & mask) { - debug_print("de_lossy_rounding_S_FX_EFX_NCOB_ECOB failed!\n"); - return -1; - } - if (data_buf[i].ECOB_Y & mask) { - debug_print("de_lossy_rounding_S_FX_EFX_NCOB_ECOB failed!\n"); - return -1; - } - - data_buf[i].FX = round_inv(data_buf[i].FX, round_used); - data_buf[i].NCOB_X = round_inv(data_buf[i].NCOB_X, round_used); - data_buf[i].NCOB_Y = round_inv(data_buf[i].NCOB_Y, round_used); - data_buf[i].EFX = round_inv(data_buf[i].EFX, round_used); - data_buf[i].ECOB_X = round_inv(data_buf[i].ECOB_X, round_used); - data_buf[i].ECOB_Y = round_inv(data_buf[i].ECOB_Y, round_used); + for (i = 0; i < samples; ++i) { + a[i].fx = be32_to_cpu(a[i].fx); + a[i].ncob_x = be32_to_cpu(a[i].ncob_x); + a[i].ncob_y = be32_to_cpu(a[i].ncob_y); } - return 0; -} - - -struct S_FX_EFX_NCOB_ECOB cal_up_model_S_FX_EFX_NCOB_ECOB - (struct S_FX_EFX_NCOB_ECOB data_buf, struct S_FX_EFX_NCOB_ECOB - model_buf, unsigned int model_value) -{ - struct S_FX_EFX_NCOB_ECOB result; - - result.EXPOSURE_FLAGS = - (uint8_t)cal_up_model(data_buf.EXPOSURE_FLAGS, - model_buf.EXPOSURE_FLAGS, model_value); - result.FX = cal_up_model(data_buf.FX, model_buf.FX, model_value); - result.NCOB_X = cal_up_model(data_buf.NCOB_X, model_buf.NCOB_X, model_value); - result.NCOB_Y = cal_up_model(data_buf.NCOB_Y, model_buf.NCOB_Y, model_value); - result.EFX = cal_up_model(data_buf.EFX, model_buf.EFX, model_value); - result.ECOB_X = cal_up_model(data_buf.ECOB_X, model_buf.ECOB_X, model_value); - result.ECOB_Y = cal_up_model(data_buf.ECOB_Y, model_buf.ECOB_Y, model_value); - - return result; } -void be_to_cpus_S_FX_EFX_NCOB_ECOB(struct S_FX_EFX_NCOB_ECOB *a, uint32_t samples) +static void be_to_cpus_f_fx_efx_ncob_ecob(struct f_fx_efx_ncob_ecob *a, int samples) { - size_t i; + int i; for (i = 0; i < samples; ++i) { - a[i].FX = be32_to_cpu(a[i].FX); - a[i].NCOB_X = be32_to_cpu(a[i].NCOB_X); - a[i].NCOB_Y = be32_to_cpu(a[i].NCOB_Y); - a[i].EFX = be32_to_cpu(a[i].EFX); - a[i].ECOB_X = be32_to_cpu(a[i].ECOB_X); - a[i].ECOB_Y = be32_to_cpu(a[i].ECOB_Y); + a[i].fx = be32_to_cpu(a[i].fx); + a[i].ncob_x = be32_to_cpu(a[i].ncob_x); + a[i].ncob_y = be32_to_cpu(a[i].ncob_y); + a[i].efx = be32_to_cpu(a[i].efx); + a[i].ecob_x = be32_to_cpu(a[i].ecob_x); + a[i].ecob_y = be32_to_cpu(a[i].ecob_y); } } /** - * @brief swap the endianness of science products form big endian to the cpu - * endianness in place + * @brief swap the endianness of uncompressed data form big endian to the cpu + * endianness (or the other way around) in place * - * @param data pointer to a data sample - * @param cmp_mode compression mode + * @param data pointer to a data sample + * @param data_size_byte size of the data in bytes + * @param data_type compression data type + * + * @returns 0 on success; non-zero on failure */ -void cmp_input_big_to_cpu_endiannessy(void *data, u_int32_t data_size_byte, - uint32_t cmp_mode) +int cmp_input_big_to_cpu_endianness(void *data, uint32_t data_size_byte, + enum cmp_data_type data_type) { - int samples = cmp_input_size_to_samples(data_size_byte, cmp_mode); + int samples = cmp_input_size_to_samples(data_size_byte, data_type); - if (!data) - return; + if (!data) /* nothing to do */ + return 0; if (samples < 0) { debug_print("Error: Can not convert data size in samples.\n"); - return; + return -1; } - if (!rdcu_supported_mode_is_used(cmp_mode)) - data = (uint8_t *)data + N_DPU_ICU_MULTI_ENTRY_HDR_SIZE; - - switch (cmp_mode) { - case MODE_RAW: - case MODE_MODEL_ZERO: - case MODE_MODEL_MULTI: - case MODE_DIFF_ZERO: - case MODE_DIFF_MULTI: + /* we do not convert the endianness of the multi entry header */ + if (!rdcu_supported_data_type_is_used(data_type)) + data = (uint8_t *)data + MULTI_ENTRY_HDR_SIZE; + + switch (data_type) { + case DATA_TYPE_IMAGETTE: + case DATA_TYPE_IMAGETTE_ADAPTIVE: + case DATA_TYPE_SAT_IMAGETTE: + case DATA_TYPE_SAT_IMAGETTE_ADAPTIVE: + case DATA_TYPE_F_CAM_IMAGETTE: + case DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE: be_to_cpus_16(data, samples); break; - case MODE_RAW_S_FX: - case MODE_MODEL_ZERO_S_FX: - case MODE_MODEL_MULTI_S_FX: - case MODE_DIFF_ZERO_S_FX: - case MODE_DIFF_MULTI_S_FX: - be_to_cpus_S_FX(data, samples); - break; - case MODE_MODEL_ZERO_S_FX_EFX: - case MODE_MODEL_MULTI_S_FX_EFX: - case MODE_DIFF_ZERO_S_FX_EFX: - case MODE_DIFF_MULTI_S_FX_EFX: - be_to_cpus_S_FX_EFX(data, samples); - break; - case MODE_MODEL_ZERO_S_FX_EFX_NCOB_ECOB: - case MODE_MODEL_MULTI_S_FX_EFX_NCOB_ECOB: - case MODE_DIFF_ZERO_S_FX_EFX_NCOB_ECOB: - case MODE_DIFF_MULTI_S_FX_EFX_NCOB_ECOB: - be_to_cpus_S_FX_EFX_NCOB_ECOB(data, samples); - break; - case MODE_MODEL_ZERO_F_FX: - case MODE_MODEL_MULTI_F_FX: - case MODE_DIFF_ZERO_F_FX: - case MODE_DIFF_MULTI_F_FX: - /* be_to_cpus_F_FX(data, samples); */ - /* break; */ - case MODE_MODEL_ZERO_F_FX_EFX: - case MODE_MODEL_MULTI_F_FX_EFX: - case MODE_DIFF_ZERO_F_FX_EFX: - case MODE_DIFF_MULTI_F_FX_EFX: - /* be_to_cpus_F_FX_EFX(data, samples); */ - /* break; */ - case MODE_MODEL_ZERO_F_FX_NCOB: - case MODE_MODEL_MULTI_F_FX_NCOB: - case MODE_DIFF_ZERO_F_FX_NCOB: - case MODE_DIFF_MULTI_F_FX_NCOB: - /* be_to_cpus_F_FX_NCOB(data, samples); */ - /* break; */ - case MODE_MODEL_ZERO_F_FX_EFX_NCOB_ECOB: - case MODE_MODEL_MULTI_F_FX_EFX_NCOB_ECOB: - case MODE_DIFF_ZERO_F_FX_EFX_NCOB_ECOB: - case MODE_DIFF_MULTI_F_FX_EFX_NCOB_ECOB: - /* be_to_cpus_F_FX_EFX_NCOB_ECOB(data, samples); */ - /* break; */ - case MODE_RAW_32: - case MODE_MODEL_ZERO_32: - case MODE_MODEL_MULTI_32: - case MODE_DIFF_ZERO_32: - case MODE_DIFF_MULTI_32: - /* be32_to_cpus(data); */ - /* break; */ - default: - debug_print("Error: Compression mode not supported.\n"); + case DATA_TYPE_OFFSET: + be_to_cpus_nc_offset(data, samples); + break; + case DATA_TYPE_BACKGROUND: + be_to_cpus_nc_background(data, samples); + break; + case DATA_TYPE_SMEARING: + be_to_cpus_smearing(data, samples); + break; + case DATA_TYPE_S_FX: + be_to_cpus_s_fx(data, samples); + break; + case DATA_TYPE_S_FX_DFX: + be_to_cpus_s_fx_efx(data, samples); break; + case DATA_TYPE_S_FX_NCOB: + be_to_cpus_s_fx_ncob(data, samples); + break; + case DATA_TYPE_S_FX_DFX_NCOB_ECOB: + be_to_cpus_s_fx_efx_ncob_ecob(data, samples); + break; + case DATA_TYPE_L_FX: + be_to_cpus_l_fx(data, samples); + break; + case DATA_TYPE_L_FX_DFX: + be_to_cpus_l_fx_efx(data, samples); + break; + case DATA_TYPE_L_FX_NCOB: + be_to_cpus_l_fx_ncob(data, samples); + break; + case DATA_TYPE_L_FX_DFX_NCOB_ECOB: + be_to_cpus_l_fx_efx_ncob_ecob(data, samples); + break; + case DATA_TYPE_F_FX: + be_to_cpus_f_fx(data, samples); + break; + case DATA_TYPE_F_FX_DFX: + be_to_cpus_f_fx_efx(data, samples); + break; + case DATA_TYPE_F_FX_NCOB: + be_to_cpus_f_fx_ncob(data, samples); + break; + case DATA_TYPE_F_FX_DFX_NCOB_ECOB: + be_to_cpus_f_fx_efx_ncob_ecob(data, samples); + break; + /* TODO: implement F_CAM conversion */ + case DATA_TYPE_F_CAM_OFFSET: + case DATA_TYPE_F_CAM_BACKGROUND: + case DATA_TYPE_UNKOWN: + default: + debug_print("Error: Can not swap endianness for this compression data type.\n"); + return -1; } + + return 0; } diff --git a/lib/cmp_entity.c b/lib/cmp_entity.c index 99aa3bd..d058b17 100644 --- a/lib/cmp_entity.c +++ b/lib/cmp_entity.c @@ -17,24 +17,30 @@ * @see Data Compression User Manual PLATO-UVIE-PL-UM-0001 */ + #include <stdint.h> #include <stdio.h> #include <string.h> #include <limits.h> -#if __has_include(<time.h>) - #include <time.h> - #include <stdlib.h> +#if defined __has_include +# if __has_include(<time.h>) +# include <time.h> +# include <stdlib.h> +# define HAS_TIME_H 1 +# endif #endif -#include "../include/cmp_entity.h" -#include "../include/cmp_support.h" -#include "../include/byteorder.h" +#include "byteorder.h" +#include "cmp_debug.h" +#include "cmp_support.h" +#include "cmp_data_types.h" +#include "cmp_entity.h" -#if __has_include(<time.h>) +#ifdef HAS_TIME_H /* Used as epoch Wed Jan 1 00:00:00 2020 */ # if defined(_WIN32) || defined(_WIN64) -const struct tm EPOCH_DATE = { 0, 0, 0, 1, 0, 120, 0, 0, 0, }; +const struct tm EPOCH_DATE = { 0, 0, 0, 1, 0, 120, 0, 0, 0 }; # else const struct tm EPOCH_DATE = { 0, 0, 0, 1, 0, 120, 0, 0, 0, 0, NULL }; # endif /* _WIN */ @@ -42,69 +48,61 @@ const struct tm EPOCH_DATE = { 0, 0, 0, 1, 0, 120, 0, 0, 0, 0, NULL }; /** - * @brief calculate the size of the compression entity header based of the data + * @brief calculate the size of the compression entity header based on the data * product type * * @param data_type compression entity data product type + * @param raw_mode_flag set this flag if the raw compression mode (CMP_MODE_RAW) is used * * @returns size of the compression entity header in bytes, 0 on unknown data * type */ -uint32_t cmp_ent_cal_hdr_size(enum cmp_ent_data_type data_type) -{ - switch (data_type) { - case DATA_TYPE_IMAGETTE: - case DATA_TYPE_F_CAM_IMAGETTE: - return IMAGETTE_HEADER_SIZE; - case DATA_TYPE_IMAGETTE_ADAPTIVE: - case DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE: - return IMAGETTE_ADAPTIVE_HEADER_SIZE; - case DATA_TYPE_SAT_IMAGETTE: - case DATA_TYPE_SAT_IMAGETTE_ADAPTIVE: - case DATA_TYPE_OFFSET: - case DATA_TYPE_BACKGROUND: - case DATA_TYPE_SMEARING: - case DATA_TYPE_S_FX: - case DATA_TYPE_S_FX_DFX: - case DATA_TYPE_S_FX_NCOB: - case DATA_TYPE_S_FX_DFX_NCOB_ECOB: - case DATA_TYPE_L_FX: - case DATA_TYPE_L_FX_DFX: - case DATA_TYPE_L_FX_NCOB: - case DATA_TYPE_L_FX_DFX_NCOB_ECOB: - case DATA_TYPE_F_FX: - case DATA_TYPE_F_FX_DFX: - case DATA_TYPE_F_FX_NCOB: - case DATA_TYPE_F_FX_DFX_NCOB_ECOB: - case DATA_TYPE_F_CAM_OFFSET: - case DATA_TYPE_F_CAM_BACKGROUND: - return NON_IMAGETTE_HEADER_SIZE; - case DATA_TYPE_UNKOWN: - return 0; +uint32_t cmp_ent_cal_hdr_size(enum cmp_data_type data_type, int raw_mode_flag) +{ + uint32_t size = 0; + + if (raw_mode_flag) { + if (cmp_data_type_valid(data_type)) + /* for raw data we do not need a specific header */ + size = GENERIC_HEADER_SIZE; + } else { + switch (data_type) { + case DATA_TYPE_IMAGETTE: + case DATA_TYPE_SAT_IMAGETTE: + case DATA_TYPE_F_CAM_IMAGETTE: + size = IMAGETTE_HEADER_SIZE; + break; + case DATA_TYPE_IMAGETTE_ADAPTIVE: + case DATA_TYPE_SAT_IMAGETTE_ADAPTIVE: + case DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE: + size = IMAGETTE_ADAPTIVE_HEADER_SIZE; + break; + case DATA_TYPE_OFFSET: + case DATA_TYPE_BACKGROUND: + case DATA_TYPE_SMEARING: + case DATA_TYPE_S_FX: + case DATA_TYPE_S_FX_DFX: + case DATA_TYPE_S_FX_NCOB: + case DATA_TYPE_S_FX_DFX_NCOB_ECOB: + case DATA_TYPE_L_FX: + case DATA_TYPE_L_FX_DFX: + case DATA_TYPE_L_FX_NCOB: + case DATA_TYPE_L_FX_DFX_NCOB_ECOB: + case DATA_TYPE_F_FX: + case DATA_TYPE_F_FX_DFX: + case DATA_TYPE_F_FX_NCOB: + case DATA_TYPE_F_FX_DFX_NCOB_ECOB: + case DATA_TYPE_F_CAM_OFFSET: + case DATA_TYPE_F_CAM_BACKGROUND: + size = NON_IMAGETTE_HEADER_SIZE; + break; + case DATA_TYPE_UNKOWN: + size = 0; + break; + } } - - if ((data_type >> RAW_BIT_DATA_TYPE_POS) & 1U) - return GENERIC_HEADER_SIZE; - - return 0; -} - - -/** - * @brief check if the compression entity data product type is supported - * - * @param data_type compression entity data product type to check - * - * @returns zero if data_type is invalid; non-zero if data_type is valid - */ - -int cmp_ent_data_type_valid(enum cmp_ent_data_type data_type) -{ - if (cmp_ent_cal_hdr_size(data_type)) - return 1; - else - return 0; + return size; } @@ -326,19 +324,23 @@ int cmp_ent_set_fine_end_time(struct cmp_entity *ent, uint16_t fine_time) /** * @brief set the data product type in the compression entity header * - * @param ent pointer to a compression entity (including the - * uncompressed data bit) + * @param ent pointer to a compression entity * @param data_type compression entity data product type + * @param raw_mode_flag set this flag if the raw compression mode (CMP_MODE_RAW) is used * * @returns 0 on success, otherwise error */ int cmp_ent_set_data_type(struct cmp_entity *ent, - enum cmp_ent_data_type data_type) + enum cmp_data_type data_type, + int raw_mode_flag) { if (!ent) return -1; + if (raw_mode_flag) + data_type |= 1U << RAW_BIT_DATA_TYPE_POS; + if (data_type > UINT16_MAX) return -1; @@ -932,7 +934,7 @@ int cmp_ent_set_non_ima_cmp_par6(struct cmp_entity *ent, uint32_t cmp_par_6_used /** - * @brief get the ASW version identifier form the compression entity header + * @brief get the ASW version identifier from the compression entity header * * @param ent pointer to a compression entity * @@ -949,7 +951,7 @@ uint32_t cmp_ent_get_version_id(struct cmp_entity *ent) /** - * @brief get the size of the compression entity form the compression entity header + * @brief get the size of the compression entity from the compression entity header * * @param ent pointer to a compression entity * @@ -970,7 +972,7 @@ uint32_t cmp_ent_get_size(struct cmp_entity *ent) /** - * @brief get the original data size form the compression entity header + * @brief get the original data size from the compression entity header * * @param ent pointer to a compression entity * @@ -991,7 +993,7 @@ uint32_t cmp_ent_get_original_size(struct cmp_entity *ent) /** - * @brief get the compression start timestamp form the compression entity header + * @brief get the compression start timestamp from the compression entity header * * @param ent pointer to a compression entity * @@ -1012,7 +1014,7 @@ uint64_t cmp_ent_get_start_timestamp(struct cmp_entity *ent) /** - * @brief get the coarse time form the compression start timestamp in the + * @brief get the coarse time from the compression start timestamp in the * compression entity header * * @returns the coarse part of the compression start timestamp on success, 0 on @@ -1029,7 +1031,7 @@ uint32_t cmp_ent_get_coarse_start_time(struct cmp_entity *ent) /** - * @brief get the fine time form the compression start timestamp in the + * @brief get the fine time from the compression start timestamp in the * compression entity header * * @returns the fine part of the compression start timestamp on success, 0 on @@ -1046,7 +1048,7 @@ uint16_t cmp_ent_get_fine_start_time(struct cmp_entity *ent) /** - * @brief get the compression end timestamp form the compression entity header + * @brief get the compression end timestamp from the compression entity header * * @param ent pointer to a compression entity * @@ -1067,7 +1069,7 @@ uint64_t cmp_ent_get_end_timestamp(struct cmp_entity *ent) /** - * @brief get the coarse time form the compression end timestamp in the + * @brief get the coarse time from the compression end timestamp in the * compression entity header * * @returns the coarse part of the compression end timestamp on success, 0 on @@ -1084,7 +1086,7 @@ uint32_t cmp_ent_get_coarse_end_time(struct cmp_entity *ent) /** - * @brief get the fine time form the compression end timestamp in the + * @brief get the fine time from the compression end timestamp in the * compression entity header * * @returns the fine part of the compression end timestamp on success, 0 on @@ -1101,30 +1103,33 @@ uint16_t cmp_ent_get_fine_end_time(struct cmp_entity *ent) /** - * @brief get data_type form the compression entity header + * @brief get data_type from the compression entity header * * @param ent pointer to a compression entity * - * @returns the data_type (including the uncompressed data bit) on success, + * @returns the data_type NOT including the uncompressed data bit on success, * DATA_TYPE_UNKOWN on error */ -enum cmp_ent_data_type cmp_ent_get_data_type(struct cmp_entity *ent) +enum cmp_data_type cmp_ent_get_data_type(struct cmp_entity *ent) { + enum cmp_data_type data_type; + if (!ent) return DATA_TYPE_UNKOWN; - enum cmp_ent_data_type data_type = be16_to_cpu(ent->data_type); + data_type = be16_to_cpu(ent->data_type); + data_type &= (1U << RAW_BIT_DATA_TYPE_POS)-1; /* remove uncompressed data flag */ - if (cmp_ent_data_type_valid(data_type)) - return data_type; - else - return DATA_TYPE_UNKOWN; + if (!cmp_data_type_valid(data_type)) + data_type = DATA_TYPE_UNKOWN; + + return data_type; } /** - * @brief get raw bit form the data_type field of the compression entity header + * @brief get the raw bit from the data_type field of the compression entity header * * @param ent pointer to a compression entity * @@ -1176,7 +1181,7 @@ uint8_t cmp_ent_get_model_value_used(struct cmp_entity *ent) /** - * @brief get model id form the compression entity header + * @brief get model id from the compression entity header * * @param ent pointer to a compression entity * @@ -1210,7 +1215,7 @@ uint8_t cmp_ent_get_model_counter(struct cmp_entity *ent) /** - * @brief get the used lossy compression parameter form the compression entity header + * @brief get the used lossy compression parameter from the compression entity header * * @param ent pointer to a compression entity * @@ -1227,7 +1232,7 @@ uint16_t cmp_ent_get_lossy_cmp_par(struct cmp_entity *ent) /** - * @brief get the used spillover threshold parameter form the (adaptive) + * @brief get the used spillover threshold parameter from the (adaptive) * imagette specific compression entity header * * @param ent pointer to a compression entity @@ -1245,7 +1250,7 @@ uint16_t cmp_ent_get_ima_spill(struct cmp_entity *ent) /** - * @brief get the used Golomb parameter form the (adaptive) imagette specific + * @brief get the used Golomb parameter from the (adaptive) imagette specific * compression entity header * * @param ent pointer to a compression entity @@ -1263,7 +1268,7 @@ uint8_t cmp_ent_get_ima_golomb_par(struct cmp_entity *ent) /** - * @brief get the used adaptive 1 spillover threshold parameter form the + * @brief get the used adaptive 1 spillover threshold parameter from the * adaptive imagette specific compression entity header * * @param ent pointer to a compression entity @@ -1281,7 +1286,7 @@ uint16_t cmp_ent_get_ima_ap1_spill(struct cmp_entity *ent) /** - * @brief get the used adaptive 1 Golomb parameter form adaptive imagette + * @brief get the used adaptive 1 Golomb parameter from adaptive imagette * specific compression entity header * * @param ent pointer to a compression entity @@ -1299,7 +1304,7 @@ uint8_t cmp_ent_get_ima_ap1_golomb_par(struct cmp_entity *ent) /** - * @brief get the used adaptive 2 spillover threshold parameter form the + * @brief get the used adaptive 2 spillover threshold parameter from the * adaptive imagette specific compression entity header * * @param ent pointer to a compression entity @@ -1317,7 +1322,7 @@ uint16_t cmp_ent_get_ima_ap2_spill(struct cmp_entity *ent) /** - * @brief get the used adaptive 2 spillover threshold parameter form the + * @brief get the used adaptive 2 spillover threshold parameter from the * adaptive imagette specific compression entity header * * @param ent pointer to a compression entity @@ -1335,7 +1340,7 @@ uint8_t cmp_ent_get_ima_ap2_golomb_par(struct cmp_entity *ent) /** - * @brief get the used spillover threshold 1 parameter form the non-imagette + * @brief get the used spillover threshold 1 parameter from the non-imagette * specific compression entity header * * @param ent pointer to a compression entity @@ -1357,7 +1362,7 @@ uint32_t cmp_ent_get_non_ima_spill1(struct cmp_entity *ent) /** - * @brief get the used compression parameter 1 form the non-imagette specific + * @brief get the used compression parameter 1 from the non-imagette specific * compression entity header * * @param ent pointer to a compression entity @@ -1375,7 +1380,7 @@ uint16_t cmp_ent_get_non_ima_cmp_par1(struct cmp_entity *ent) /** - * @brief get the used spillover threshold 2 parameter form the non-imagette + * @brief get the used spillover threshold 2 parameter from the non-imagette * specific compression entity header * * @param ent pointer to a compression entity @@ -1397,7 +1402,7 @@ uint32_t cmp_ent_get_non_ima_spill2(struct cmp_entity *ent) /** - * @brief get the used compression parameter 2 form the non-imagette specific + * @brief get the used compression parameter 2 from the non-imagette specific * compression entity header * * @param ent pointer to a compression entity @@ -1415,7 +1420,7 @@ uint16_t cmp_ent_get_non_ima_cmp_par2(struct cmp_entity *ent) /** - * @brief get the used spillover threshold 3 parameter form the non-imagette + * @brief get the used spillover threshold 3 parameter from the non-imagette * specific compression entity header * * @param ent pointer to a compression entity @@ -1437,7 +1442,7 @@ uint32_t cmp_ent_get_non_ima_spill3(struct cmp_entity *ent) /** - * @brief get the used compression parameter 3 form the non-imagette specific + * @brief get the used compression parameter 3 from the non-imagette specific * compression entity header * * @param ent pointer to a compression entity @@ -1455,7 +1460,7 @@ uint16_t cmp_ent_get_non_ima_cmp_par3(struct cmp_entity *ent) /** - * @brief get the used spillover threshold 4 parameter form the non-imagette + * @brief get the used spillover threshold 4 parameter from the non-imagette * specific compression entity header * * @param ent pointer to a compression entity @@ -1477,7 +1482,7 @@ uint32_t cmp_ent_get_non_ima_spill4(struct cmp_entity *ent) /** - * @brief get the used compression parameter 4 form the non-imagette specific + * @brief get the used compression parameter 4 from the non-imagette specific * compression entity header * * @param ent pointer to a compression entity @@ -1495,7 +1500,7 @@ uint16_t cmp_ent_get_non_ima_cmp_par4(struct cmp_entity *ent) /** - * @brief get the used spillover threshold 5 parameter form the non-imagette + * @brief get the used spillover threshold 5 parameter from the non-imagette * specific compression entity header * * @param ent pointer to a compression entity @@ -1517,7 +1522,7 @@ uint32_t cmp_ent_get_non_ima_spill5(struct cmp_entity *ent) /** - * @brief get the used compression parameter 5 form the non-imagette specific + * @brief get the used compression parameter 5 from the non-imagette specific * compression entity header * * @param ent pointer to a compression entity @@ -1535,7 +1540,7 @@ uint16_t cmp_ent_get_non_ima_cmp_par5(struct cmp_entity *ent) /** - * @brief get the used spillover threshold 6 parameter form the non-imagette + * @brief get the used spillover threshold 6 parameter from the non-imagette * specific compression entity header * * @param ent pointer to a compression entity @@ -1557,7 +1562,7 @@ uint32_t cmp_ent_get_non_ima_spill6(struct cmp_entity *ent) /** - * @brief get the used compression parameter 6 form the non-imagette specific + * @brief get the used compression parameter 6 from the non-imagette specific * compression entity header * * @param ent pointer to a compression entity @@ -1588,7 +1593,7 @@ uint16_t cmp_ent_get_non_ima_cmp_par6(struct cmp_entity *ent) void *cmp_ent_get_data_buf(struct cmp_entity *ent) { - enum cmp_ent_data_type data_type; + enum cmp_data_type data_type; if (!ent) return NULL; @@ -1634,7 +1639,7 @@ void *cmp_ent_get_data_buf(struct cmp_entity *ent) /** - * @brief copy the data form a compression entity to a buffer + * @brief copy the data from a compression entity to a buffer * * @param ent pointer to the compression entity containing the compressed data * @param data_buf pointer where the destination data buffer where the @@ -1699,7 +1704,8 @@ ssize_t cmp_ent_get_cmp_data(struct cmp_entity *ent, uint32_t *data_buf, static uint32_t cmp_ent_get_hdr_size(struct cmp_entity *ent) { - return cmp_ent_cal_hdr_size(cmp_ent_get_data_type(ent)); + return cmp_ent_cal_hdr_size(cmp_ent_get_data_type(ent), + cmp_ent_get_data_type_raw_bit(ent)); } @@ -1727,108 +1733,67 @@ uint32_t cmp_ent_get_cmp_data_size(struct cmp_entity *ent) /** - * @brief set the parameters in the non-imagette specific compression - * entity header + * @brief write the compression parameters from a compression configuration + * into the compression entity header + * @note no compressed data are put into the entity and no change of the entity + * size * - * @param ent pointer to a compression entity - * @param info decompression information structure - * - * @returns 0 on success, otherwise error + * @param ent pointer to a compression entity + * @param cfg pointer to a compression configuration + * @param cmp_size_bits size of the compressed data in bits * - * @warning this functions is not implemented jet + * @returns 0 on success, negative on error */ -static int cmp_ent_write_non_ima_parameters(struct cmp_entity *ent, struct cmp_info *info) +int cmp_ent_write_cmp_pars(struct cmp_entity *ent, const struct cmp_cfg *cfg, + int cmp_size_bits) { - if (!info) - return -1; - (void)ent; - - printf("not implemented jet!\n"); - return -1; -#if 0 - if (cmp_ent_set_non_ima_spill1(ent, info->)) - return -1; - if (cmp_ent_set_non_ima_cmp_par1(ent, info->)) - return -1; - - if (cmp_ent_set_non_ima_spill2(ent, info->)) - return -1; - if (cmp_ent_set_non_ima_cmp_par2(ent, info->)) - return -1; - - if (cmp_ent_set_non_ima_spill3(ent, info->)) - return -1; - if (cmp_ent_set_non_ima_cmp_par3(ent, info->)) - return -1; - - if (cmp_ent_set_non_ima_spill4(ent, info->)) - return -1; - if (cmp_ent_set_non_ima_cmp_par4(ent, info->)) - return -1; + uint32_t ent_cmp_data_size; - if (cmp_ent_set_non_ima_spill5(ent, info->)) - return -1; - if (cmp_ent_set_non_ima_cmp_par5(ent, info->)) + if (!cfg) return -1; - if (cmp_ent_set_non_ima_spill6(ent, info->)) - return -1; - if (cmp_ent_set_non_ima_cmp_par6(ent, info->)) + if (cmp_size_bits < 0) return -1; - return 0; -#endif -} - -/** - * @brief write the compression parameters in the entity header based on the data - * product type set in the entity and the provided decompression information - * struct - * - * @param ent pointer to a compression entity - * @param info decompression information structure - * @param cfg compression configuration structure for adaptive compression - * parameters (can be NULL) - * - * @returns 0 on success, negative on error - */ - -static int cmp_ent_write_cmp_pars(struct cmp_entity *ent, struct cmp_info *info, - struct cmp_cfg *cfg) -{ - uint32_t ent_cmp_data_size; - - if (!info) + if (cfg->data_type != cmp_ent_get_data_type(ent)) { + debug_print("Error: The entity data product type dos not match the configuration data product type.\n"); return -1; + } ent_cmp_data_size = cmp_ent_get_cmp_data_size(ent); /* check if the entity can hold the compressed data */ - if (ent_cmp_data_size < cmp_bit_to_4byte(info->cmp_size)) + if (ent_cmp_data_size < cmp_bit_to_4byte((unsigned int)cmp_size_bits)) { + debug_print("Error: The entity size is to small to hold the compressed data.\n"); return -2; + } /* set compression parameter fields in the generic entity header */ - if (cmp_ent_set_original_size(ent, cmp_cal_size_of_data(info->samples_used, - info->cmp_mode_used))) + if (cmp_ent_set_original_size(ent, cmp_cal_size_of_data(cfg->samples, + cfg->data_type))) return -1; - if (cmp_ent_set_cmp_mode(ent, info->cmp_mode_used)) + if (cmp_ent_set_cmp_mode(ent, cfg->cmp_mode)) return -1; - if (cmp_ent_set_model_value(ent, info->model_value_used)) + if (cmp_ent_set_model_value(ent, cfg->model_value)) return -1; - if (cmp_ent_set_lossy_cmp_par(ent, info->round_used)) + if (cmp_ent_set_lossy_cmp_par(ent, cfg->round)) return -1; - if (cmp_ent_get_data_type_raw_bit(ent)) + if (cfg->cmp_mode == CMP_MODE_RAW) { + /* check the raw data bit */ + if (!cmp_ent_get_data_type_raw_bit(ent)) { + debug_print("Error: The entity's raw data bit is not set, but the configuration contains raw data.\n"); + return -1; + } /* no specific header is used for raw data we are done */ return 0; + } switch (cmp_ent_get_data_type(ent)) { case DATA_TYPE_IMAGETTE_ADAPTIVE: case DATA_TYPE_SAT_IMAGETTE_ADAPTIVE: case DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE: - if (!cfg) - return -1; if (cmp_ent_set_ima_ap1_spill(ent, cfg->ap1_spill)) return -1; if (cmp_ent_set_ima_ap1_golomb_par(ent, cfg->ap1_golomb_par)) @@ -1841,14 +1806,44 @@ static int cmp_ent_write_cmp_pars(struct cmp_entity *ent, struct cmp_info *info, case DATA_TYPE_IMAGETTE: case DATA_TYPE_SAT_IMAGETTE: case DATA_TYPE_F_CAM_IMAGETTE: - if (cmp_ent_set_ima_spill(ent, info->spill_used)) + if (cmp_ent_set_ima_spill(ent, cfg->spill)) return -1; - if (cmp_ent_set_ima_golomb_par(ent, info->golomb_par_used)) + if (cmp_ent_set_ima_golomb_par(ent, cfg->golomb_par)) return -1; break; case DATA_TYPE_OFFSET: case DATA_TYPE_BACKGROUND: case DATA_TYPE_SMEARING: + if (cmp_ent_set_non_ima_cmp_par1(ent, cfg->cmp_par_mean)) + return -1; + if (cmp_ent_set_non_ima_spill1(ent, cfg->spill_mean)) + return -1; + + if (cmp_ent_set_non_ima_spill2(ent, cfg->cmp_par_variance)) + return -1; + if (cmp_ent_set_non_ima_cmp_par2(ent, cfg->spill_variance)) + return -1; + + if (cmp_ent_set_non_ima_cmp_par3(ent, cfg->cmp_par_pixels_error)) + return -1; + if (cmp_ent_set_non_ima_spill3(ent, cfg->spill_pixels_error)) + return -1; + + if (cmp_ent_set_non_ima_cmp_par4(ent, 0)) + return -1; + if (cmp_ent_set_non_ima_spill4(ent, 0)) + return -1; + + if (cmp_ent_set_non_ima_cmp_par5(ent, 0)) + return -1; + if (cmp_ent_set_non_ima_spill5(ent, 0)) + return -1; + + if (cmp_ent_set_non_ima_cmp_par6(ent, 0)) + return -1; + if (cmp_ent_set_non_ima_spill6(ent, 0)) + return -1; + break; case DATA_TYPE_S_FX: case DATA_TYPE_S_FX_DFX: case DATA_TYPE_S_FX_NCOB: @@ -1861,12 +1856,43 @@ static int cmp_ent_write_cmp_pars(struct cmp_entity *ent, struct cmp_info *info, case DATA_TYPE_F_FX_DFX: case DATA_TYPE_F_FX_NCOB: case DATA_TYPE_F_FX_DFX_NCOB_ECOB: + if (cmp_ent_set_non_ima_cmp_par1(ent, cfg->cmp_par_exp_flags)) + return -1; + if (cmp_ent_set_non_ima_spill1(ent, cfg->spill_exp_flags)) + return -1; + + if (cmp_ent_set_non_ima_cmp_par2(ent, cfg->cmp_par_fx)) + return -1; + if (cmp_ent_set_non_ima_spill2(ent, cfg->spill_fx)) + return -1; + + if (cmp_ent_set_non_ima_cmp_par3(ent, cfg->cmp_par_ncob)) + return -1; + if (cmp_ent_set_non_ima_spill3(ent, cfg->spill_ncob)) + return -1; + + if (cmp_ent_set_non_ima_cmp_par4(ent, cfg->cmp_par_efx)) + return -1; + if (cmp_ent_set_non_ima_spill4(ent, cfg->spill_efx)) + return -1; + + if (cmp_ent_set_non_ima_cmp_par5(ent, cfg->cmp_par_ecob)) + return -1; + if (cmp_ent_set_non_ima_spill5(ent, cfg->spill_ecob)) + return -1; + + if (cmp_ent_set_non_ima_cmp_par6(ent, cfg->cmp_par_fx_cob_variance)) + return -1; + if (cmp_ent_set_non_ima_spill6(ent, cfg->spill_fx_cob_variance)) + return -1; + + break; case DATA_TYPE_F_CAM_OFFSET: case DATA_TYPE_F_CAM_BACKGROUND: - if (cmp_ent_write_non_ima_parameters(ent, info)) + /* TODO: fix this*/ return -1; break; - case DATA_TYPE_UNKOWN: /* fall through */ + case DATA_TYPE_UNKOWN: default: return -1; } @@ -1876,12 +1902,94 @@ static int cmp_ent_write_cmp_pars(struct cmp_entity *ent, struct cmp_info *info, /** - * @brief create a compression entity by setting the size of the - * compression entity and the data product type in the entity header + * @brief write the parameters from the RDCU decompression information structure + * in the compression entity header + * @note no compressed data are put into the entity and no change of the entity + * size + * + * @param ent pointer to a compression entity + * @param info pointer to a decompression information structure + * @param cfg pointer to a compression configuration structure for adaptive + * compression parameters (can be NULL if non adaptive data_type is used) + * + * @returns 0 on success, negative on error + */ + +int cmp_ent_write_rdcu_cmp_pars(struct cmp_entity *ent, const struct cmp_info *info, + const struct cmp_cfg *cfg) +{ + uint32_t ent_cmp_data_size; + enum cmp_data_type data_type = cmp_ent_get_data_type(ent); + + if (!info) + return -1; + + ent_cmp_data_size = cmp_ent_get_cmp_data_size(ent); + + /* check if the entity can hold the compressed data */ + if (ent_cmp_data_size < cmp_bit_to_4byte(info->cmp_size)) { + debug_print("Error: The entity size is to small to hold the compressed data.\n"); + return -2; + } + + if (!rdcu_supported_data_type_is_used(data_type)) { + debug_print("Error: The compression data type is not one of the types supported by the RDCU.\n"); + return -1; + } + + /* set compression parameter fields in the generic entity header */ + if (cmp_ent_set_original_size(ent, cmp_cal_size_of_data(info->samples_used, DATA_TYPE_IMAGETTE))) + return -1; + if (cmp_ent_set_cmp_mode(ent, info->cmp_mode_used)) + return -1; + if (cmp_ent_set_model_value(ent, info->model_value_used)) + return -1; + if (cmp_ent_set_lossy_cmp_par(ent, info->round_used)) + return -1; + + if (cmp_ent_get_data_type_raw_bit(ent) != raw_mode_is_used(info->cmp_mode_used)) { + debug_print("Error: The raw bit is set in data product type filed, but no raw compression mode is used.\n"); + return -1; + } + if (raw_mode_is_used(info->cmp_mode_used)) + /* no specific header is used for raw data we are done */ + return 0; + + if (cmp_ent_set_ima_spill(ent, info->spill_used)) + return -1; + if (cmp_ent_set_ima_golomb_par(ent, info->golomb_par_used)) + return -1; + + /* use the adaptive imagette parameter from the compression configuration + * if an adaptive imagette compression data type is ent in the entity + */ + if (cmp_ap_imagette_data_type_is_used(data_type)) { + if (!cfg) { + debug_print("Error: Need the compression configuration to get the adaptive parameters.\n"); + return -1; + } + if (cmp_ent_set_ima_ap1_spill(ent, cfg->ap1_spill)) + return -1; + if (cmp_ent_set_ima_ap1_golomb_par(ent, cfg->ap1_golomb_par)) + return -1; + if (cmp_ent_set_ima_ap2_spill(ent, cfg->ap2_spill)) + return -1; + if (cmp_ent_set_ima_ap2_golomb_par(ent, cfg->ap2_golomb_par)) + return -1; + } + + return 0; +} + + +/** + * @brief create a compression entity by setting the size of the compression + * entity and the data product type in the entity header * * @param ent pointer to a compression entity; if NULL, the function - * returns the needed size + * returns the needed size * @param data_type compression entity data product type + * @param raw_mode_flag set this flag if the raw compression mode (CMP_MODE_RAW) is used * @param cmp_size_byte size of the compressed data in bytes * * @note if the entity size is smaller than the largest header, the function @@ -1890,12 +1998,12 @@ static int cmp_ent_write_cmp_pars(struct cmp_entity *ent, struct cmp_info *info, * @returns the size of the compression entity or 0 on error */ -size_t cmp_ent_create(struct cmp_entity *ent, enum cmp_ent_data_type data_type, - uint32_t cmp_size_byte) +uint32_t cmp_ent_create(struct cmp_entity *ent, enum cmp_data_type data_type, + int raw_mode_flag, uint32_t cmp_size_byte) { int err; - uint32_t hdr_size = cmp_ent_cal_hdr_size(data_type); - size_t ent_size = hdr_size + cmp_size_byte; + uint32_t hdr_size = cmp_ent_cal_hdr_size(data_type, raw_mode_flag); + uint32_t ent_size = hdr_size + cmp_size_byte; uint32_t ent_size_cpy = ent_size; if (!hdr_size) @@ -1904,6 +2012,9 @@ size_t cmp_ent_create(struct cmp_entity *ent, enum cmp_ent_data_type data_type, if (cmp_size_byte > CMP_ENTITY_MAX_SIZE) return 0; + if (ent_size > CMP_ENTITY_MAX_SIZE) + return 0; + if (ent_size < sizeof(struct cmp_entity)) ent_size = sizeof(struct cmp_entity); @@ -1916,7 +2027,7 @@ size_t cmp_ent_create(struct cmp_entity *ent, enum cmp_ent_data_type data_type, if (err) return 0; - err = cmp_ent_set_data_type(ent, data_type); + err = cmp_ent_set_data_type(ent, data_type, raw_mode_flag); if (err) return 0; @@ -1927,35 +2038,37 @@ size_t cmp_ent_create(struct cmp_entity *ent, enum cmp_ent_data_type data_type, /** * @brief create a compression entity and set the header fields * - * @note this function simplifies the entity setup by creating a entity and + * @note this function simplifies the entity set up by creating an entity and * setting the header fields in one function call + * @note no compressed data are put into the entity * * @param ent pointer to a compression entity; if NULL, the * function returns the needed size - * @param data_type compression entity data product type - * @param version_id applications software version identifier + * @param version_id applications software version identifier * @param start_time compression start timestamp (coarse and fine) * @param end_time compression end timestamp (coarse and fine) * @param model_id model identifier * @param model_counter model counter - * @param info decompression information structure - * @param cfg compression configuration structure for adaptive - * compression parameters (can be NULL) + * @param cfg pointer to compression configuration (can be NULL) + * @param cmp_size_bits length of the compressed data in bits * * @returns the size of the compression entity or 0 on error */ -size_t cmp_ent_build(struct cmp_entity *ent, enum cmp_ent_data_type data_type, - uint32_t version_id, uint64_t start_time, - uint64_t end_time, uint16_t model_id, uint8_t model_counter, - struct cmp_info *info, struct cmp_cfg *cfg) +size_t cmp_ent_build(struct cmp_entity *ent, uint32_t version_id, + uint64_t start_time, uint64_t end_time, uint16_t model_id, + uint8_t model_counter, struct cmp_cfg *cfg, int cmp_size_bits) { uint32_t ent_size; - if (!info) + if (!cfg) return 0; - ent_size = cmp_ent_create(ent, data_type, cmp_bit_to_4byte(info->cmp_size)); + if (cmp_size_bits < 0) + return 0; + + ent_size = cmp_ent_create(ent, cfg->data_type, cfg->cmp_mode == CMP_MODE_RAW, + cmp_bit_to_4byte((unsigned int)cmp_size_bits)); if (!ent_size) return 0; @@ -1970,7 +2083,7 @@ size_t cmp_ent_build(struct cmp_entity *ent, enum cmp_ent_data_type data_type, return 0; if (cmp_ent_set_model_counter(ent, model_counter)) return 0; - if (cmp_ent_write_cmp_pars(ent, info, cfg)) + if (cmp_ent_write_cmp_pars(ent, cfg, cmp_size_bits)) return 0; } @@ -1979,82 +2092,116 @@ size_t cmp_ent_build(struct cmp_entity *ent, enum cmp_ent_data_type data_type, /** - * @brief read in read in a imagette compression entity header to a info struct + * @brief read in an imagette compression entity header to a + * compression configuration * - * @param ent pointer to a compression entity - * @param info pointer to decompression information structure - * to store the read values + * @param ent pointer to a compression entity + * @param cfg pointer to a compression configuration * * @returns 0 on success; otherwise error */ -int cmp_ent_read_imagette_header(struct cmp_entity *ent, struct cmp_info *info) +int cmp_ent_read_header(struct cmp_entity *ent, struct cmp_cfg *cfg) { - uint32_t original_size; - uint32_t sample_size; + int samples; - if (!ent) - return -1; - - if (!info) - return -1; - - info->cmp_mode_used = cmp_ent_get_cmp_mode(ent); - info->model_value_used = cmp_ent_get_model_value_used(ent); - info->round_used = cmp_ent_get_lossy_cmp_par(ent); - info->spill_used = cmp_ent_get_ima_spill(ent); - info->golomb_par_used = cmp_ent_get_ima_golomb_par(ent); - info->cmp_size = cmp_ent_get_cmp_data_size(ent)*CHAR_BIT; - - sample_size = size_of_a_sample(info->cmp_mode_used); - if (!sample_size) { - info->samples_used = 0; + cfg->data_type = cmp_ent_get_data_type(ent); + if (!cmp_data_type_valid(cfg->data_type)) { + debug_print("Error: Compression data type not supported.\n"); return -1; } - original_size = cmp_ent_get_original_size(ent); - if (original_size % sample_size != 0) { - fprintf(stderr, "Error: original_size and cmp_mode compression header field are not compatible.\n"); - info->samples_used = 0; + cfg->cmp_mode = cmp_ent_get_cmp_mode(ent); + cfg->model_value = cmp_ent_get_model_value_used(ent); + cfg->round = cmp_ent_get_lossy_cmp_par(ent); + cfg->buffer_length = cmp_ent_get_cmp_data_size(ent); + + samples = cmp_input_size_to_samples(cmp_ent_get_original_size(ent), cfg->data_type); + if (samples < 0) { + debug_print("Error: original_size and data product type in the compression header field are not compatible.\n"); + cfg->samples = 0; return -1; } - info->samples_used = original_size / sample_size; + cfg->samples = samples; - if (cmp_ent_get_data_type_raw_bit(ent) != raw_mode_is_used(info->cmp_mode_used)) { - fprintf(stderr, "Error: The raw bit is set in Data Product Type Filed, but no raw compression mode is used.\n"); + if (cmp_ent_get_data_type_raw_bit(ent) != raw_mode_is_used(cfg->cmp_mode)) { + debug_print("Error: The raw bit is set in data product type filed, but no raw compression mode is used.\n"); return -1; } - return 0; -} - -#if 0 -int cmp_ent_read_adaptive_imagette_header(struct cmp_entity *ent, struct cmp_info *info) -{ - if (!ent) - return -1; + cfg->icu_output_buf = cmp_ent_get_data_buf(ent); - if (!info) - return -1; - - if (cmp_ent_get_imagette_header(ent, info)) + switch (cfg->data_type) { + case DATA_TYPE_IMAGETTE_ADAPTIVE: + case DATA_TYPE_SAT_IMAGETTE_ADAPTIVE: + case DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE: + cfg->ap1_golomb_par = cmp_ent_get_ima_ap1_golomb_par(ent); + cfg->ap1_spill = cmp_ent_get_ima_ap1_spill(ent); + cfg->ap2_golomb_par = cmp_ent_get_ima_ap2_golomb_par(ent); + cfg->ap2_spill = cmp_ent_get_ima_ap2_spill(ent); + /* fall through */ + case DATA_TYPE_IMAGETTE: + case DATA_TYPE_SAT_IMAGETTE: + case DATA_TYPE_F_CAM_IMAGETTE: + cfg->spill = cmp_ent_get_ima_spill(ent); + cfg->golomb_par = cmp_ent_get_ima_golomb_par(ent); + break; + case DATA_TYPE_OFFSET: + case DATA_TYPE_BACKGROUND: + case DATA_TYPE_SMEARING: + cfg->cmp_par_mean = cmp_ent_get_non_ima_cmp_par1(ent); + cfg->spill_mean = cmp_ent_get_non_ima_spill1(ent); + cfg->cmp_par_variance = cmp_ent_get_non_ima_cmp_par2(ent); + cfg->spill_variance = cmp_ent_get_non_ima_spill2(ent); + cfg->cmp_par_pixels_error = cmp_ent_get_non_ima_cmp_par3(ent); + cfg->spill_pixels_error = cmp_ent_get_non_ima_spill3(ent); + break; + case DATA_TYPE_S_FX: + case DATA_TYPE_S_FX_DFX: + case DATA_TYPE_S_FX_NCOB: + case DATA_TYPE_S_FX_DFX_NCOB_ECOB: + case DATA_TYPE_L_FX: + case DATA_TYPE_L_FX_DFX: + case DATA_TYPE_L_FX_NCOB: + case DATA_TYPE_L_FX_DFX_NCOB_ECOB: + case DATA_TYPE_F_FX: + case DATA_TYPE_F_FX_DFX: + case DATA_TYPE_F_FX_NCOB: + case DATA_TYPE_F_FX_DFX_NCOB_ECOB: + cfg->cmp_par_exp_flags = cmp_ent_get_non_ima_cmp_par1(ent); + cfg->spill_exp_flags = cmp_ent_get_non_ima_spill1(ent); + cfg->cmp_par_fx = cmp_ent_get_non_ima_cmp_par2(ent); + cfg->spill_fx = cmp_ent_get_non_ima_spill2(ent); + cfg->cmp_par_ncob = cmp_ent_get_non_ima_cmp_par3(ent); + cfg->spill_ncob = cmp_ent_get_non_ima_spill3(ent); + cfg->cmp_par_efx = cmp_ent_get_non_ima_cmp_par4(ent); + cfg->spill_efx = cmp_ent_get_non_ima_spill4(ent); + cfg->cmp_par_ecob = cmp_ent_get_non_ima_cmp_par5(ent); + cfg->spill_ecob = cmp_ent_get_non_ima_spill5(ent); + cfg->cmp_par_fx_cob_variance = cmp_ent_get_non_ima_cmp_par6(ent); + cfg->spill_fx_cob_variance = cmp_ent_get_non_ima_spill6(ent); + break; + case DATA_TYPE_F_CAM_OFFSET: + case DATA_TYPE_F_CAM_BACKGROUND: + /* TODO: fix this*/ + return -1; + break; + case DATA_TYPE_UNKOWN: /* fall through */ + default: return -1; - /* info->ap1_cmp_size_byte = cmp_ent_get_ap1_cmp_size_byte(ent); */ - /* info->ap2_cmp_size_byte = cmp_ent_get_ap2_cmp_size_byte(ent); */ - /* get ap1_spill and ap1/2 gpar*/ + } return 0; } -#endif -#if __has_include(<time.h>) +#ifdef HAS_TIME_H /* * @brief Covert a calendar time expressed as a struct tm object to time since * epoch as a time_t object. The function interprets the input structure * as representing Universal Coordinated Time (UTC). - * @note timegm is a GNU C Library extensions, not standardized. This function + * @note timegm is a GNU C Library extension, not standardized. This function * is used as a portable alternative * @note The function is thread-unsafe * @@ -2067,9 +2214,9 @@ int cmp_ent_read_adaptive_imagette_header(struct cmp_entity *ent, struct cmp_inf static time_t my_timegm(struct tm *tm) { -#if defined(_WIN32) || defined(_WIN64) +# if defined(_WIN32) || defined(_WIN64) return _mkgmtime(tm); -#else +# else time_t ret; char *tz; @@ -2086,14 +2233,14 @@ static time_t my_timegm(struct tm *tm) unsetenv("TZ"); tzset(); return ret; -#endif +# endif } /* * @brief Generate a timestamp for the compression header * - * @param ts pointer to object of type struct timespec of the timestamp time, null for now + * @param ts pointer to an object of type struct timespec of the timestamp time, null for now * * @returns returns compression header timestamp or 0 on error */ @@ -2186,7 +2333,7 @@ void cmp_ent_print(struct cmp_entity *ent) /** - * @brief parse the generic compressed entity header + * @brief parses the generic compressed entity header * * @param ent pointer to a compression entity */ @@ -2197,13 +2344,14 @@ static void cmp_ent_parse_generic_header(struct cmp_entity *ent) model_value_used, model_id, model_counter, lossy_cmp_par_used, start_coarse_time, end_coarse_time; uint16_t start_fine_time, end_fine_time; - enum cmp_ent_data_type data_type; + enum cmp_data_type data_type; int raw_bit; version_id = cmp_ent_get_version_id(ent); if (version_id & CMP_TOOL_VERSION_ID_BIT) { uint16_t major = (version_id & 0x7FFF0000U) >> 16U; uint16_t minor = version_id & 0xFFFFU; + printf("Compressed with cmp_tool version: %u.%02u\n", major, minor); } else printf("ICU ASW Version ID: %u\n", version_id); @@ -2226,10 +2374,11 @@ static void cmp_ent_parse_generic_header(struct cmp_entity *ent) end_fine_time = cmp_ent_get_fine_end_time(ent); printf("Compression Fine End Time: %d\n", end_fine_time); -#if __has_include(<time.h>) +#ifdef HAS_TIME_H { struct tm epoch_date = EPOCH_DATE; time_t time = my_timegm(&epoch_date) + start_coarse_time; + printf("Data were compressed on (local time): %s", ctime(&time)); } #endif @@ -2319,17 +2468,26 @@ static void cmp_ent_parese_adaptive_imagette_header(struct cmp_entity *ent) static void cmp_ent_parese_specific_header(struct cmp_entity *ent) { - enum cmp_ent_data_type data_type = cmp_ent_get_data_type(ent); + enum cmp_data_type data_type = cmp_ent_get_data_type(ent); + + if (cmp_ent_get_data_type_raw_bit(ent)) { + printf("Uncompressed data bit is set no specific header is used.\n"); + return; + } switch (data_type) { case DATA_TYPE_IMAGETTE: + case DATA_TYPE_SAT_IMAGETTE: + case DATA_TYPE_F_CAM_IMAGETTE: cmp_ent_parese_imagette_header(ent); break; case DATA_TYPE_IMAGETTE_ADAPTIVE: + case DATA_TYPE_SAT_IMAGETTE_ADAPTIVE: + case DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE: cmp_ent_parese_adaptive_imagette_header(ent); break; default: - printf("Data Product Type not supported!\n"); + printf("For this data product type no parse functions is implemented!\n"); break; } } @@ -2347,3 +2505,4 @@ void cmp_ent_parse(struct cmp_entity *ent) cmp_ent_parese_specific_header(ent); } + diff --git a/lib/cmp_guess.c b/lib/cmp_guess.c index 265b9c5..c173814 100644 --- a/lib/cmp_guess.c +++ b/lib/cmp_guess.c @@ -17,12 +17,14 @@ * dataset */ +#include <limits.h> #include <stdio.h> #include <stdlib.h> #include <string.h> -#include "../include/cmp_icu.h" -#include "../include/cmp_guess.h" +#include "cmp_data_types.h" +#include "cmp_icu.h" +#include "cmp_guess.h" /* how often the model is updated before it is rested */ @@ -67,6 +69,43 @@ uint16_t cmp_guess_model_value(int n_model_updates) } +/** + * @brief get a good spill threshold parameter for the selected Golomb parameter + * and compression mode + * + * @param golomb_par Golomb parameter + * @param cmp_mode compression mode + * + * @returns a good spill parameter (optimal for zero escape mechanism) + * @warning icu compression not support yet! + */ + +uint32_t cmp_rdcu_get_good_spill(unsigned int golomb_par, enum cmp_mode cmp_mode) +{ + const uint32_t LUT_RDCU_MULIT[MAX_RDCU_GOLOMB_PAR+1] = {0, 8, 16, 23, + 30, 36, 44, 51, 58, 64, 71, 77, 84, 90, 97, 108, 115, 121, 128, + 135, 141, 148, 155, 161, 168, 175, 181, 188, 194, 201, 207, 214, + 229, 236, 242, 250, 256, 263, 269, 276, 283, 290, 296, 303, 310, + 317, 324, 330, 336, 344, 351, 358, 363, 370, 377, 383, 391, 397, + 405, 411, 418, 424, 431, 452 }; + + if (zero_escape_mech_is_used(cmp_mode)) + return get_max_spill(golomb_par, DATA_TYPE_IMAGETTE); + + if (cmp_mode == CMP_MODE_MODEL_MULTI) { + if (golomb_par > MAX_RDCU_GOLOMB_PAR) + return 0; + else + return LUT_RDCU_MULIT[golomb_par]; + } + + if (cmp_mode == CMP_MODE_DIFF_MULTI) + return CMP_GOOD_SPILL_DIFF_MULTI; + + return 0; +} + + /** * @brief guess a good configuration with pre_cal_method * @@ -79,18 +118,17 @@ uint16_t cmp_guess_model_value(int n_model_updates) static uint32_t pre_cal_method(struct cmp_cfg *cfg) { uint32_t g; - uint32_t cmp_size; - uint32_t cmp_size_best = ~0U; + int cmp_size, cmp_size_best = INT_MAX; uint32_t golomb_par_best = 0; uint32_t spill_best = 0; for (g = MIN_RDCU_GOLOMB_PAR; g < MAX_RDCU_GOLOMB_PAR; g++) { - uint32_t s = cmp_get_good_spill(g, cfg->cmp_mode); + uint32_t s = cmp_rdcu_get_good_spill(g, cfg->cmp_mode); cfg->golomb_par = g; cfg->spill = s; - cmp_size = cmp_encode_data(cfg); - if (cmp_size == 0) { + cmp_size = icu_compress_data(cfg); + if (cmp_size <= 0) { return 0; } else if (cmp_size < cmp_size_best) { cmp_size_best = cmp_size; @@ -120,8 +158,7 @@ static uint32_t brute_force(struct cmp_cfg *cfg) uint32_t g, s; uint32_t n_cal_steps = 0, last = 0; const uint32_t max_cal_steps = CMP_GUESS_MAX_CAL_STEPS; - uint32_t cmp_size; - uint32_t cmp_size_best = ~0U; + int cmp_size, cmp_size_best = INT_MAX; uint32_t golomb_par_best = 0; uint32_t spill_best = 0; uint32_t percent; @@ -134,12 +171,12 @@ static uint32_t brute_force(struct cmp_cfg *cfg) fflush(stdout); for (g = MIN_RDCU_GOLOMB_PAR; g < MAX_RDCU_GOLOMB_PAR; g++) { - for (s = MIN_RDCU_SPILL; s < get_max_spill(g, cfg->cmp_mode); s++) { + for (s = MIN_RDCU_SPILL; s < get_max_spill(g, cfg->data_type); s++) { cfg->golomb_par = g; cfg->spill = s; - cmp_size = cmp_encode_data(cfg); - if (cmp_size == 0) { + cmp_size = icu_compress_data(cfg); + if (cmp_size <= 0) { return 0; } else if (cmp_size < cmp_size_best) { cmp_size_best = cmp_size; @@ -185,19 +222,19 @@ static void add_rdcu_pars_internal(struct cmp_cfg *cfg) cfg->ap2_golomb_par = cfg->golomb_par + 1; } - cfg->ap1_spill = cmp_get_good_spill(cfg->ap1_golomb_par, cfg->cmp_mode); - cfg->ap2_spill = cmp_get_good_spill(cfg->ap2_golomb_par, cfg->cmp_mode); + cfg->ap1_spill = cmp_rdcu_get_good_spill(cfg->ap1_golomb_par, cfg->cmp_mode); + cfg->ap2_spill = cmp_rdcu_get_good_spill(cfg->ap2_golomb_par, cfg->cmp_mode); if (model_mode_is_used(cfg->cmp_mode)) { - cfg->rdcu_data_adr = DEFAULT_CFG_MODEL.rdcu_data_adr; - cfg->rdcu_model_adr = DEFAULT_CFG_MODEL.rdcu_model_adr; - cfg->rdcu_new_model_adr = DEFAULT_CFG_MODEL.rdcu_new_model_adr; - cfg->rdcu_buffer_adr = DEFAULT_CFG_MODEL.rdcu_buffer_adr; + cfg->rdcu_data_adr = CMP_DEF_IMA_MODEL_RDCU_DATA_ADR; + cfg->rdcu_model_adr = CMP_DEF_IMA_MODEL_RDCU_MODEL_ADR; + cfg->rdcu_new_model_adr = CMP_DEF_IMA_MODEL_RDCU_UP_MODEL_ADR; + cfg->rdcu_buffer_adr = CMP_DEF_IMA_MODEL_RDCU_BUFFER_ADR; } else { - cfg->rdcu_data_adr = DEFAULT_CFG_DIFF.rdcu_data_adr; - cfg->rdcu_model_adr = DEFAULT_CFG_DIFF.rdcu_model_adr; - cfg->rdcu_new_model_adr = DEFAULT_CFG_DIFF.rdcu_new_model_adr; - cfg->rdcu_buffer_adr = DEFAULT_CFG_DIFF.rdcu_buffer_adr; + cfg->rdcu_data_adr = CMP_DEF_IMA_DIFF_RDCU_DATA_ADR; + cfg->rdcu_model_adr = CMP_DEF_IMA_DIFF_RDCU_MODEL_ADR; + cfg->rdcu_new_model_adr = CMP_DEF_IMA_DIFF_RDCU_UP_MODEL_ADR; + cfg->rdcu_buffer_adr = CMP_DEF_IMA_DIFF_RDCU_BUFFER_ADR; } } @@ -220,7 +257,6 @@ uint32_t cmp_guess(struct cmp_cfg *cfg, int level) { size_t size; struct cmp_cfg work_cfg; - int err; uint32_t cmp_size = 0; if (!cfg) @@ -228,38 +264,28 @@ uint32_t cmp_guess(struct cmp_cfg *cfg, int level) if (!cfg->input_buf) return 0; + if (model_mode_is_used(cfg->cmp_mode)) + if (!cfg->model_buf) + return 0; - if (!rdcu_supported_mode_is_used(cfg->cmp_mode)) { + if (!rdcu_supported_cmp_mode_is_used(cfg->cmp_mode)) { printf("This compression mode is not implied yet.\n"); return 0; } /* make a working copy of the input data (and model) because the - * following function works inplace */ + * following function works inplace + */ work_cfg = *cfg; - work_cfg.input_buf = NULL; - work_cfg.model_buf = NULL; work_cfg.icu_new_model_buf = NULL; work_cfg.icu_output_buf = NULL; work_cfg.buffer_length = 0; + work_cfg.data_type = DATA_TYPE_IMAGETTE; /* TODO: adapt to others data types */ - size = cfg->samples * size_of_a_sample(work_cfg.cmp_mode); + size = cmp_cal_size_of_data(cfg->samples, cfg->data_type); if (size == 0) goto error; - work_cfg.input_buf = malloc(size); - if (!work_cfg.input_buf) { - printf("malloc() failed!\n"); - goto error; - } - memcpy(work_cfg.input_buf, cfg->input_buf, size); - - if (cfg->model_buf && model_mode_is_used(cfg->cmp_mode)) { - work_cfg.model_buf = malloc(size); - if (!work_cfg.model_buf) { - printf("malloc() failed!\n"); - goto error; - } - memcpy(work_cfg.model_buf, cfg->model_buf, size); + if (model_mode_is_used(cfg->cmp_mode)) { work_cfg.icu_new_model_buf = malloc(size); if (!work_cfg.icu_new_model_buf) { printf("malloc() failed!\n"); @@ -267,14 +293,6 @@ uint32_t cmp_guess(struct cmp_cfg *cfg, int level) } } - err = cmp_pre_process(&work_cfg); - if (err) - goto error; - - err = cmp_map_to_pos(&work_cfg); - if (err) - goto error; - /* find the best parameters */ switch (level) { case 3: @@ -294,8 +312,6 @@ uint32_t cmp_guess(struct cmp_cfg *cfg, int level) if (!cmp_size) goto error; - free(work_cfg.input_buf); - free(work_cfg.model_buf); free(work_cfg.icu_new_model_buf); cfg->golomb_par = work_cfg.golomb_par; @@ -303,16 +319,15 @@ uint32_t cmp_guess(struct cmp_cfg *cfg, int level) cfg->model_value = cmp_guess_model_value(num_model_updates); - if (rdcu_supported_mode_is_used(cfg->cmp_mode)) + if (rdcu_supported_data_type_is_used(cfg->data_type)) add_rdcu_pars_internal(cfg); - cfg->buffer_length = ((cmp_size + 32)&~0x1FU)/(size_of_a_sample(work_cfg.cmp_mode)*8); + /* TODO: check that for non-imagette data */ + cfg->buffer_length = ((cmp_size + 32)&~0x1FU)/(size_of_a_sample(cfg->data_type)*8); return cmp_size; error: - free(work_cfg.input_buf); - free(work_cfg.model_buf); free(work_cfg.icu_new_model_buf); return 0; } diff --git a/lib/cmp_icu.c b/lib/cmp_icu.c index 5409045..057ee64 100644 --- a/lib/cmp_icu.c +++ b/lib/cmp_icu.c @@ -15,1761 +15,2396 @@ * * @brief software compression library * @see Data Compression User Manual PLATO-UVIE-PL-UM-0001 + * + * To compress data, first create a compression configuration with the + * cmp_cfg_icu_create() function. + * Then set the different data buffers with the data to compressed, the model + * data and the compressed data with the cmp_cfg_icu_buffers() function. + * Then set the compression data type specific compression parameters. For + * imagette data use the cmp_cfg_icu_imagette() function. For flux and center of + * brightness data use the cmp_cfg_fx_cob() function. And for background, offset + * and smearing data use the cmp_cfg_aux() function. + * Finally, you can compress the data with the icu_compress_data() function. */ -#include <stdio.h> #include <stdint.h> #include <string.h> #include <limits.h> -#include "../include/cmp_support.h" -#include "../include/cmp_data_types.h" -#include "../include/cmp_icu.h" -#include "../include/byteorder.h" -#include "../include/cmp_debug.h" +#include "byteorder.h" +#include "cmp_debug.h" +#include "cmp_data_types.h" +#include "cmp_support.h" +#include "cmp_icu.h" + + +/* pointer to a code word generation function */ +typedef uint32_t (*generate_cw_f_pt)(uint32_t value, uint32_t encoder_par1, + uint32_t encoder_par2, uint32_t *cw); + + +/* structure to hold a setup to encode a value */ +struct encoder_setupt { + generate_cw_f_pt generate_cw_f; /* pointer to the code word generation function */ + int (*encode_method_f)(uint32_t data, uint32_t model, int stream_len, + const struct encoder_setupt *setup); /* pointer to the encoding function */ + uint32_t *bitstream_adr; /* start address of the compressed data bitstream */ + uint32_t max_stream_len; /* maximum length of the bitstream/icu_output_buf in bits */ + uint32_t encoder_par1; /* encoding parameter 1 */ + uint32_t encoder_par2; /* encoding parameter 2 */ + uint32_t spillover_par; /* outlier parameter */ + uint32_t lossy_par; /* lossy compression parameter */ + uint32_t max_data_bits; /* how many bits are needed to represent the highest possible value */ +}; /** - * @brief check if the compressor configuration is valid for a SW compression, - * see the user manual for more information (PLATO-UVIE-PL-UM-0001). + * @brief create a ICU compression configuration * - * @param cfg configuration contains all parameters required for compression - * @param info compressor information contains information of an executed - * compression (can be NULL) + * @param data_type compression data product types + * @param cmp_mode compression mode + * @param model_value model weighting parameter (only need for model compression mode) + * @param lossy_par lossy rounding parameter (use CMP_LOSSLESS for lossless compression) * - * @returns 0 when configuration is valid, invalid configuration otherwise + * @returns compression configuration containing the chosen parameters; + * on error the data_type record is set to DATA_TYPE_UNKOWN */ -int icu_cmp_cfg_valid(const struct cmp_cfg *cfg, struct cmp_info *info) +struct cmp_cfg cmp_cfg_icu_create(enum cmp_data_type data_type, enum cmp_mode cmp_mode, + uint32_t model_value, uint32_t lossy_par) { - int cfg_invalid = 0; - - if (!cfg) { - debug_print("Error: compression configuration structure is NULL.\n"); - return -1; - } + int cfg_valid; + struct cmp_cfg cfg = {0}; - if (!info) - debug_print("Warning: compressor information structure is NULL.\n"); + cfg.data_type = data_type; + cfg.cmp_mode = cmp_mode; + cfg.model_value = model_value; + cfg.round = lossy_par; - if (info) - info->cmp_err = 0; /* reset errors */ - - if (cfg->input_buf == NULL) { - debug_print("Error: The input_buf buffer for the data to be compressed is NULL.\n"); - cfg_invalid++; - } + cfg_valid = cmp_cfg_icu_gen_par_is_valid(&cfg); + if (!cfg_valid) + cfg.data_type = DATA_TYPE_UNKOWN; - if (cfg->samples == 0) - debug_print("Warning: The samples parameter is 0. No data are compressed. This behavior may not be intended.\n"); + return cfg; +} - /* icu_output_buf can be NULL if rdcu compression is used */ - if (cfg->icu_output_buf == NULL) { - debug_print("Error: The icu_output_buf buffer for the compressed data is NULL.\n"); - cfg_invalid++; - } - if (cfg->buffer_length == 0 && cfg->samples != 0) { - debug_print("Error: The buffer_length is set to 0. There is no space to store the compressed data.\n"); - cfg_invalid++; - } +/** + * @brief setup of the different data buffers for an ICU compression + * + * @param cfg pointer to a compression configuration (created + * with the cmp_cfg_icu_create() function) + * @param data_to_compress pointer to the data to be compressed + * @param data_samples length of the data to be compressed measured in + * data samples/entitys (multi entity header not + * included by imagette data) + * @param model_of_data pointer to model data buffer (can be NULL if no + * model compression mode is used) + * @param updated_model pointer to store the updated model for the next + * model mode compression (can be the same as the model_of_data + * buffer for in-place update or NULL if updated model is not needed) + * @param compressed_data pointer to the compressed data buffer (can be NULL) + * @param compressed_data_len_samples length of the compressed_data buffer in + * measured in the same units as the data_samples + * + * @returns the size of the compressed_data buffer on success; 0 if the + * parameters are invalid + */ - if (cfg->icu_output_buf == cfg->input_buf) { - debug_print("Error: The icu_output_buf buffer is the same as the input_buf buffer.\n"); - cfg_invalid++; +size_t cmp_cfg_icu_buffers(struct cmp_cfg *cfg, void *data_to_compress, + uint32_t data_samples, void *model_of_data, + void *updated_model, uint32_t *compressed_data, + uint32_t compressed_data_len_samples) +{ + if (!cfg) { + debug_print("Error: pointer to the compression configuration structure is NULL.\n"); + return 0; } - if (model_mode_is_used(cfg->cmp_mode)) { - if (cfg->model_buf == NULL) { - debug_print("Error: The model_buf buffer for the model data is NULL.\n"); - cfg_invalid++; - } - - if (cfg->model_buf == cfg->input_buf) { - debug_print("Error: The model_buf buffer is the same as the input_buf buffer.\n"); - cfg_invalid++; - } - - if (cfg->model_buf == cfg->icu_output_buf) { - debug_print("Error: The model_buf buffer is the same as the icu_output_buf buffer.\n"); - cfg_invalid++; - } - - if (cfg->icu_new_model_buf == cfg->input_buf) { - debug_print("Error: The icu_new_model_buf buffer is the same as the input_buf buffer.\n"); - cfg_invalid++; - } - - if (cfg->icu_new_model_buf == cfg->icu_output_buf) { - debug_print("Error: The icu_output_buf buffer is the same as the icu_output_buf buffer.\n"); - cfg_invalid++; - } - } + cfg->input_buf = data_to_compress; + cfg->model_buf = model_of_data; + cfg->samples = data_samples; + cfg->icu_new_model_buf = updated_model; + cfg->icu_output_buf = compressed_data; + cfg->buffer_length = compressed_data_len_samples; - if (raw_mode_is_used(cfg->cmp_mode)) { - if (cfg->samples > cfg->buffer_length) { - debug_print("Error: The buffer_length is to small to hold the data form the input_buf.\n"); - cfg_invalid++; - } - } else { - if (cfg->samples*size_of_a_sample(cfg->cmp_mode) < - cfg->buffer_length*sizeof(uint16_t)/3) /* TODO: have samples and buffer_lengt the same unit */ - debug_print("Warning: The size of the icu_output_buf is 3 times smaller than the input_buf. This is probably unintentional.\n"); - } + if (!cmp_cfg_icu_buffers_is_valid(cfg)) + return 0; + return cmp_cal_size_of_data(compressed_data_len_samples, cfg->data_type); +} - if (!(diff_mode_is_used(cfg->cmp_mode) - || model_mode_is_used(cfg->cmp_mode) - || raw_mode_is_used(cfg->cmp_mode))) { - debug_print("Error: selected cmp_mode: %u is not supported\n.", - cfg->cmp_mode); - if (info) - info->cmp_err |= 1UL << CMP_MODE_ERR_BIT; - cfg_invalid++; - } - if (raw_mode_is_used(cfg->cmp_mode)) /* additional checks are not needed for the raw mode */ - return -cfg_invalid; +/** + * @brief set up the configuration parameters for an ICU imagette compression + * + * @param cfg pointer to a compression configuration (created + * with the cmp_cfg_icu_create() function) + * @param cmp_par imagette compression parameter (Golomb parameter) + * @param spillover_par imagette spillover threshold parameter + * + * @returns 0 if parameters are valid, non-zero if parameters are invalid + */ - if (model_mode_is_used(cfg->cmp_mode)) { - if (cfg->model_value > MAX_MODEL_VALUE) { - debug_print("Error: selected model_value: %u is invalid. Largest supported value is: %lu.\n", - cfg->model_value, MAX_MODEL_VALUE); - if (info) - info->cmp_err |= 1UL << MODEL_VALUE_ERR_BIT; - cfg_invalid++; - } - } +int cmp_cfg_icu_imagette(struct cmp_cfg *cfg, uint32_t cmp_par, + uint32_t spillover_par) +{ + if (!cfg) + return -1; - if (cfg->golomb_par < MIN_ICU_GOLOMB_PAR || - cfg->golomb_par > MAX_ICU_GOLOMB_PAR) { - debug_print("Error: The selected Golomb parameter: %u is not supported. The Golomb parameter has to be between [%lu, %u].\n", - cfg->golomb_par, MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); - if (info) - info->cmp_err |= 1UL << CMP_PAR_ERR_BIT; - cfg_invalid++; - } + cfg->golomb_par = cmp_par; + cfg->spill = spillover_par; - if (cfg->spill < MIN_ICU_SPILL) { - debug_print("Error: The selected spillover threshold value: %u is too small. Smallest possible spillover value is: %lu.\n", - cfg->spill, MIN_ICU_SPILL); - if (info) - info->cmp_err |= 1UL << CMP_PAR_ERR_BIT; - cfg_invalid++; - } + if (!cmp_cfg_imagette_is_valid(cfg)) + return -1; - if (cfg->spill > get_max_spill(cfg->golomb_par, cfg->cmp_mode)) { - debug_print("Error: The selected spillover threshold value: %u is too large for the selected Golomb parameter: %u, the largest possible spillover value in the selected compression mode is: %u.\n", - cfg->spill, cfg->golomb_par, - get_max_spill(cfg->golomb_par, cfg->cmp_mode)); - if (info) - info->cmp_err |= 1UL << CMP_PAR_ERR_BIT; - cfg_invalid++; - } + return 0; +} -#ifdef ADAPTIVE_CHECK_ENA - /* - * ap1_spill and ap2_spill are not used for the icu_compression - */ - if (cfg->ap1_spill > get_max_spill(cfg->ap1_golomb_par, cfg->cmp_mode)) { - if (info) - info->cmp_err |= 1UL << AP1_CMP_PAR_ERR_BIT; - cfg_invalid++; - } +/** + * @brief set up of the configuration parameters for a flux/COB compression + * @note not all parameters are needed for every flux/COB compression data type + * + * @param cfg pointer to a compression configuration (created + * with the cmp_cfg_icu_create() function) + * @param cmp_par_exp_flags exposure flags compression parameter + * @param spillover_exp_flags exposure flags spillover threshold parameter + * @param cmp_par_fx normal flux compression parameter + * @param spillover_fx normal flux spillover threshold parameter + * @param cmp_par_ncob normal center of brightness compression parameter + * @param spillover_ncob normal center of brightness spillover threshold parameter + * @param cmp_par_efx extended flux compression parameter + * @param spillover_efx extended flux spillover threshold parameter + * @param cmp_par_ecob extended center of brightness compression parameter + * @param spillover_ecob extended center of brightness spillover threshold parameter + * @param cmp_par_fx_cob_variance flux/COB variance compression parameter + * @param spillover_fx_cob_variance flux/COB variance spillover threshold parameter + * + * @returns 0 if parameters are valid, non-zero if parameters are invalid + */ - if (cfg->ap2_spill > get_max_spill(cfg->ap2_golomb_par, cfg->cmp_mode)) { - if (info) - info->cmp_err |= 1UL << AP2_CMP_PAR_ERR_BIT; - cfg_invalid++; - } -#endif +int cmp_cfg_fx_cob(struct cmp_cfg *cfg, + uint32_t cmp_par_exp_flags, uint32_t spillover_exp_flags, + uint32_t cmp_par_fx, uint32_t spillover_fx, + uint32_t cmp_par_ncob, uint32_t spillover_ncob, + uint32_t cmp_par_efx, uint32_t spillover_efx, + uint32_t cmp_par_ecob, uint32_t spillover_ecob, + uint32_t cmp_par_fx_cob_variance, uint32_t spillover_fx_cob_variance) +{ + if (!cfg) + return -1; - if (cfg->round > MAX_ICU_ROUND) { - debug_print("Error: selected round parameter: %u is not supported. Largest supported value is: %lu.\n", - cfg->round, MAX_ICU_ROUND); - cfg_invalid++; - } + cfg->cmp_par_exp_flags = cmp_par_exp_flags; + cfg->cmp_par_fx = cmp_par_fx; + cfg->cmp_par_ncob = cmp_par_ncob; + cfg->cmp_par_efx = cmp_par_efx; + cfg->cmp_par_ecob = cmp_par_ecob; + cfg->cmp_par_fx_cob_variance = cmp_par_fx_cob_variance; + + cfg->spill_exp_flags = spillover_exp_flags; + cfg->spill_fx = spillover_fx; + cfg->spill_ncob = spillover_ncob; + cfg->spill_efx = spillover_efx; + cfg->spill_ecob = spillover_ecob; + cfg->spill_fx_cob_variance = spillover_fx_cob_variance; + + if (!cmp_cfg_fx_cob_is_valid(cfg)) + return -1; - return -(cfg_invalid); + return 0; } /** - * @brief sets the compression information used based on the compression - * configuration - * - * @param cfg compression configuration struct - * @param info compressor information struct to set the used compression - * parameters (can be NULL) - * - * @note set cmp_size, ap1_cmp_size, ap2_cmp_size will be set to 0 - * - * @returns 0 on success, error otherwise + * @brief set up of the configuration parameters for an auxiliary science data compression + * @note auxiliary compression data types are: DATA_TYPE_OFFSET, DATA_TYPE_BACKGROUND, + DATA_TYPE_SMEARING, DATA_TYPE_F_CAM_OFFSET, DATA_TYPE_F_CAM_BACKGROUND + * @note not all parameters are needed for the every auxiliary compression data types + * + * @param cfg pointer to a compression configuration ( + * created with the cmp_cfg_icu_create() function) + * @param cmp_par_mean mean compression parameter + * @param spillover_mean mean spillover threshold parameter + * @param cmp_par_variance variance compression parameter + * @param spillover_variance variance spillover threshold parameter + * @param cmp_par_pixels_error outlier pixels number compression parameter + * @param spillover_pixels_error outlier pixels number spillover threshold parameter + * + * @returns 0 if parameters are valid, non-zero if parameters are invalid */ - -static int set_info(struct cmp_cfg *cfg, struct cmp_info *info) +int cmp_cfg_aux(struct cmp_cfg *cfg, + uint32_t cmp_par_mean, uint32_t spillover_mean, + uint32_t cmp_par_variance, uint32_t spillover_variance, + uint32_t cmp_par_pixels_error, uint32_t spillover_pixels_error) { if (!cfg) return -1; - if (cfg->cmp_mode > UINT8_MAX) - return -1; + cfg->cmp_par_mean = cmp_par_mean; + cfg->cmp_par_variance = cmp_par_variance; + cfg->cmp_par_pixels_error = cmp_par_pixels_error; - if (cfg->round > UINT8_MAX) - return -1; + cfg->spill_mean = spillover_mean; + cfg->spill_variance = spillover_variance; + cfg->spill_pixels_error = spillover_pixels_error; - if (cfg->model_value > UINT8_MAX) + if (!cmp_cfg_aux_is_valid(cfg)) return -1; - if (info) { - info->cmp_err = 0; - info->cmp_mode_used = (uint8_t)cfg->cmp_mode; - info->model_value_used = (uint8_t)cfg->model_value; - info->round_used = (uint8_t)cfg->round; - info->spill_used = cfg->spill; - info->golomb_par_used = cfg->golomb_par; - info->samples_used = cfg->samples; - info->cmp_size = 0; - info->ap1_cmp_size = 0; - info->ap2_cmp_size = 0; - info->rdcu_new_model_adr_used = cfg->rdcu_new_model_adr; - info->rdcu_cmp_adr_used = cfg->rdcu_buffer_adr; - } return 0; } /** - * @brief 1d-differentiating pre-processing and rounding of a uint16_t data buffer - * - * @note change the data_buf in-place - * @note output is I[0] = I[0], I[i] = I[i] - I[i-1], where i is 1,2,..samples-1 + * @brief map a signed value into a positive value range * - * @param data_buf pointer to the uint16_t formatted data buffer to process - * @param samples amount of data samples in the data buffer - * @param round number of bits to round; if zero no rounding takes place + * @param value_to_map signed value to map + * @param max_data_bits how many bits are needed to represent the + * highest possible value * - * @returns 0 on success, error otherwise + * @returns the positive mapped value */ -static int diff_16(uint16_t *data_buf, unsigned int samples, unsigned int round) +static uint32_t map_to_pos(uint32_t value_to_map, unsigned int max_data_bits) { - size_t i; - - if (!data_buf) - return -1; - - lossy_rounding_16(data_buf, samples, round); - - for (i = samples - 1; i > 0; i--) { - /* possible underflow is intended */ - data_buf[i] = data_buf[i] - data_buf[i-1]; + uint32_t result; + uint32_t mask = (~0U >> (32 - max_data_bits)); /* mask the used bits */ + + value_to_map &= mask; + if (value_to_map >> (max_data_bits - 1)) { /* check the leading signed bit */ + value_to_map |= ~mask; /* convert to 32-bit signed integer */ + /* map negative values to uneven numbers */ + result = (-value_to_map) * 2 - 1; /* possible integer overflow is intended */ + } else { + /* map positive values to even numbers */ + result = value_to_map * 2; /* possible integer overflow is intended */ } - return 0; + + return result; } /** - * @brief 1d-differentiating pre-processing and rounding of a uint32_t data buffer - * - * @note change the data_buf in-place - * @note output is I_0 = I_0, I_i = I_i - I_i-1, where i is the array index - * - * @param data_buf pointer to the uint32_t formatted data buffer to process - * @param samples amount of data samples in the data_buf buffer - * @param round number of bits to round; if zero no rounding takes place - * - * @returns 0 on success, error otherwise + * @brief put the value of up to 32 bits into a bitstream + * + * @param value the value to put + * @param n_bits number of bits to put in the bitstream + * @param bit_offset bit index where the bits will be put, seen from + * the very beginning of the bitstream + * @param bitstream_adr this is the pointer to the beginning of the + * bitstream (can be NULL) + * @param max_stream_len maximum length of the bitstream in bits; is + * ignored if bitstream_adr is NULL + * + * @returns length in bits of the generated bitstream on success; returns + * negative in case of erroneous input; returns CMP_ERROR_SAMLL_BUF if + * the bitstream buffer is too small to put the value in the bitstream */ -static int diff_32(uint32_t *data_buf, unsigned int samples, unsigned int round) +static int put_n_bits32(uint32_t value, unsigned int n_bits, int bit_offset, + uint32_t *bitstream_adr, unsigned int max_stream_len) { - size_t i; + uint32_t *local_adr; + uint32_t mask; + unsigned int shiftRight, shiftLeft, bitsLeft, bitsRight; + int stream_len = (int)(n_bits + (unsigned int)bit_offset); /* overflow results in a negative return value */ - if (!data_buf) + /* Leave in case of erroneous input */ + if (bit_offset < 0) return -1; - lossy_rounding_32(data_buf, samples, round); + if (n_bits == 0) + return stream_len; - for (i = samples - 1; i > 0; i--) { - /* possible underflow is intended */ - data_buf[i] = data_buf[i] - data_buf[i-1]; + if (n_bits > 32) + return -1; + + /* Do we need to write data to the bitstream? */ + if (!bitstream_adr) + return stream_len; + + /* Check if bitstream buffer is large enough */ + if ((unsigned int)stream_len > max_stream_len) { + debug_print("Error: The buffer for the compressed data is too small to hold the compressed data. Try a larger buffer_length parameter.\n"); + return CMP_ERROR_SAMLL_BUF; } - return 0; -} + /* (M) is the n_bits parameter large enough to cover all value bits; the + * calculations can be re-used in the unsegmented code, so we have no overhead + */ + shiftRight = 32 - n_bits; + mask = 0xFFFFFFFFU >> shiftRight; + value &= mask; -/** - * @brief 1d-differentiating pre-processing and round of a S_FX data buffer - * - * @note change the data_buf in-place - * @note output is I_0 = I_0, I_i = I_i - I_i-1, where i is the array index - * - * @param data pointer to a S_FX data buffer - * @param samples amount of data samples in the data buffer - * @param round number of bits to round; if zero no rounding takes place - * - * @returns 0 on success, error otherwise - */ + /* Separate the bit_offset into word offset (set local_adr pointer) and local bit offset (bitsLeft) */ + local_adr = bitstream_adr + (bit_offset >> 5); + bitsLeft = bit_offset & 0x1F; -static int diff_S_FX(struct S_FX *data, unsigned int samples, unsigned int - round) -{ - size_t i; - int err; + /* Calculate the bitsRight for the unsegmented case. If bitsRight is + * negative we need to split the value over two words + */ + bitsRight = shiftRight - bitsLeft; - if (!data) - return -1; + if ((int)bitsRight >= 0) { + /* UNSEGMENTED + * + *|-----------|XXXXX|----------------| + * bitsLeft n bitsRight + * + * -> to get the mask: + * shiftRight = bitsLeft + bitsRight = 32 - n + * shiftLeft = bitsRight = 32 - n - bitsLeft = shiftRight - bitsLeft + */ - err = lossy_rounding_S_FX(data, samples, round); - if (err) - return err; + shiftLeft = bitsRight; + + /* generate the mask, the bits for the values will be true + * shiftRight = 32 - n_bits; see (M) above! + * mask = (0XFFFFFFFF >> shiftRight) << shiftLeft; see (M) above! + */ + mask <<= shiftLeft; + value <<= shiftLeft; + + /* clear the destination with inverse mask */ + *(local_adr) &= ~mask; + + /* assign the value */ + *(local_adr) |= value; + + } else { + /* SEGMENTED + * + *|-----------------------------|XXX| |XX|------------------------------| + * bitsLeft n1 n2 bitsRight + * + * -> to get the mask part 1: + * shiftRight = bitsLeft + * n1 = n - (bitsLeft + n - 32) = 32 - bitsLeft + * + * -> to get the mask part 2: + * n2 = bitsLeft + n - 32 = -(32 - n - bitsLeft) = -(bitsRight_UNSEGMENTED) + * shiftLeft = 32 - n2 = 32 - (bitsLeft + n - 32) = 64 - bitsLeft - n + * + */ + + unsigned int n2 = -bitsRight; + + /* part 1: */ + shiftRight = bitsLeft; + mask = 0XFFFFFFFFU >> shiftRight; + + /* clear the destination with inverse mask */ + *(local_adr) &= ~mask; + + /* assign the value part 1 */ + *(local_adr) |= (value >> n2); + + /* part 2: */ + /* adjust address */ + local_adr += 1; + shiftLeft = 32 - n2; + mask = 0XFFFFFFFFU >> n2; - for (i = samples - 1; i > 0; i--) { - /* possible underflow is intended */ - data[i] = sub_S_FX(data[i], data[i-1]); + /* clear the destination */ + *(local_adr) &= mask; + + /* assign the value part 2 */ + *(local_adr) |= (value << shiftLeft); } - return 0; + return stream_len; } /** - * @brief 1d-differentiating pre-processing and rounding of a S_FX_EFX data buffer - * - * @note change the data_buf in-place - * @note output is I_0 = I_0, I_i = I_i - I_i-1, where i is the array index + * @brief forms the codeword according to the Rice code * - * @param data pointer to a S_FX_EFX data buffer - * @param samples amount of data samples in the data buffer - * @param round number of bits to round; if zero no rounding takes place + * @param value value to be encoded + * @param m Golomb parameter, only m's which are power of 2 are allowed + * @param log2_m Rice parameter, is log_2(m) calculate outside function + * for better performance + * @param cw address were the encode code word is stored * - * @returns 0 on success, error otherwise + * @returns the length of the formed code word in bits + * @note no check if the generated code word is not longer than 32 bits! */ -static int diff_S_FX_EFX(struct S_FX_EFX *data, unsigned int samples, unsigned - int round) +static uint32_t rice_encoder(uint32_t value, uint32_t m, uint32_t log2_m, + uint32_t *cw) { - size_t i; - int err; + uint32_t g; /* quotient of value/m */ + uint32_t q; /* quotient code without ending zero */ + uint32_t r; /* remainder of value/m */ + uint32_t rl; /* remainder length */ - if (!data) - return -1; + g = value >> log2_m; /* quotient, number of leading bits */ + q = (1U << g) - 1; /* prepare the quotient code without ending zero */ - err = lossy_rounding_S_FX_EFX(data, samples, round); - if (err) - return err; + r = value & (m-1); /* calculate the remainder */ + rl = log2_m + 1; /* length of the remainder (+1 for the 0 in the quotient code) */ + *cw = (q << rl) | r; /* put the quotient and remainder code together */ + /* + * NOTE: If log2_m = 31 -> rl = 32, (q << rl) leads to an undefined + * behavior. However, in this case, a valid code with a maximum of 32 + * bits can only be formed if q = 0. Any shift with 0 << x always + * results in 0, which forms the correct codeword in this case. For + * performance reasons, this undefined behaviour is not caught. + */ - for (i = samples - 1; i > 0; i--) { - /* possible underflow is intended */ - data[i] = sub_S_FX_EFX(data[i], data[i-1]); - } - return 0; + return rl + g; /* calculate the length of the code word */ } /** - * @brief 1d-differentiating pre-processing and rounding of a S_FX_NCOB data buffer - * - * @note change the data_buf in-place - * @note output is I_0 = I_0, I_i = I_i - I_i-1, where i is the array index + * @brief forms a codeword according to the Golomb code * - * @param data pointer to a S_FX_NCOB data buffer - * @param samples amount of data samples in the data buffer - * @param round number of bits to round; if zero no rounding takes place + * @param value value to be encoded + * @param m Golomb parameter (have to be bigger than 0) + * @param log2_m is log_2(m) calculate outside function for better + * performance + * @param cw address were the formed code word is stored * - * @returns 0 on success, error otherwise + * @returns the length of the formed code word in bits + * @note no check if the generated code word is not longer than 32 bits! */ -static int diff_S_FX_NCOB(struct S_FX_NCOB *data, unsigned int samples, unsigned - int round) +static uint32_t golomb_encoder(uint32_t value, uint32_t m, uint32_t log2_m, + uint32_t *cw) { - size_t i; - int err; - - if (!data) - return -1; + uint32_t len0, b, g, q, lg; + uint32_t len; + uint32_t cutoff; - err = lossy_rounding_S_FX_NCOB(data, samples, round); - if (err) - return err; + len0 = log2_m + 1; /* codeword length in group 0 */ + cutoff = (1U << (log2_m + 1)) - m; /* members in group 0 */ - for (i = samples - 1; i > 0; i--) { - /* possible underflow is intended */ - data[i] = sub_S_FX_NCOB(data[i], data[i-1]); + if (value < cutoff) { /* group 0 */ + *cw = value; + len = len0; + } else { /* other groups */ + g = (value-cutoff) / m; /* this group is which one */ + b = cutoff << 1; /* form the base codeword */ + lg = len0 + g; /* it has lg remainder bits */ + q = (1U << g) - 1; /* prepare the left side in unary */ + *cw = (q << (len0+1)) + b + (value-cutoff) - g*m; /* composed codeword */ + len = lg + 1; /* length of the codeword */ } - return 0; + return len; } /** - * @brief 1d-differentiating pre-processing and rounding of a S_FX_EFX_NCOB_ECOB data buffer - * - * @note change the data_buf in-place - * @note output is I_0 = I_0, I_i = I_i - I_i-1, where i is the array index + * @brief generate a code word without an outlier mechanism and put in the + * bitstream * - * @param data pointer to a S_FX_EFX_NCOB_ECOB data buffer - * @param samples amount of data samples in the data buffer - * @param round number of bits to round; if zero no rounding takes place + * @param value value to encode in the bitstream + * @param stream_len length of the bitstream in bits + * @param setup pointer to the encoder setup * - * @returns 0 on success, error otherwise + * @returns the bit length of the bitstream with the added encoded value on + * success; negative on error, CMP_ERROR_SAMLL_BUF if the bitstream buffer + * is too small to put the value in the bitstream */ -static int diff_S_FX_EFX_NCOB_ECOB(struct S_FX_EFX_NCOB_ECOB *data, unsigned int - samples, unsigned int round) +static int encode_normal(uint32_t value, int stream_len, + const struct encoder_setupt *setup) { - size_t i; - int err; - - if (!data) - return -1; + uint32_t code_word, cw_len; - err = lossy_rounding_S_FX_EFX_NCOB_ECOB(data, samples, round); - if (err) - return err; - - for (i = samples - 1; i > 0; i--) { - /* possible underflow is intended */ - data[i] = sub_S_FX_EFX_NCOB_ECOB(data[i], data[i-1]); - } + cw_len = setup->generate_cw_f(value, setup->encoder_par1, + setup->encoder_par2, &code_word); - return 0; + return put_n_bits32(code_word, cw_len, stream_len, setup->bitstream_adr, + setup->max_stream_len); } /** - * @brief model pre-processing and rounding of a uint16_t data buffer + * @brief subtract the model from the data, encode the result and put it into + * bitstream, for encoding outlier use the zero escape symbol mechanism * - * @note overwrite the data_buf in-place with the result - * @note update the model_buf in-place if up_model_buf = NULL + * @param data data to encode + * @param model model of the data (0 if not used) + * @param stream_len length of the bitstream in bits + * @param setup pointer to the encoder setup * - * @param data_buf pointer to the uint16_t data buffer to process - * @param model_buf pointer to the model buffer of the data to process - * @param up_model_buf pointer to the updated model buffer can be NULL - * @param samples amount of data samples in the data_buf and model_buf buffer - * @param model_value model weighting parameter - * @param round number of bits to round; if zero no rounding takes place + * @returns the bit length of the bitstream with the added encoded value on + * success; negative on error, CMP_ERROR_SAMLL_BUF if the bitstream buffer + * is too small to put the value in the bitstream * - * @returns 0 on success, error otherwise + * @note no check if the data or model are in the allowed range + * @note no check if the setup->spillover_par is in the allowed range */ -static int model_16(uint16_t *data_buf, uint16_t *model_buf, uint16_t *up_model_buf, - unsigned int samples, unsigned int model_value, unsigned int round) +static int encode_value_zero(uint32_t data, uint32_t model, int stream_len, + const struct encoder_setupt *setup) { - size_t i; + data -= model; /* possible underflow is intended */ - if (!data_buf) - return -1; + data = map_to_pos(data, setup->max_data_bits); - if (!model_buf) - return -1; + /* For performance reasons, we check to see if there is an outlier + * before adding one, rather than the other way around: + * data++; + * if (data < setup->spillover_par && data != 0) + * return ... + */ + if (data < (setup->spillover_par - 1)) { /* detect non-outlier */ + data++; /* add 1 to every value so we can use 0 as escape symbol */ + return encode_normal(data, stream_len, setup); + } - if (model_value > MAX_MODEL_VALUE) - return -1; + data++; /* add 1 to every value so we can use 0 as escape symbol */ - if (!up_model_buf) - up_model_buf = model_buf; + /* use zero as escape symbol */ + stream_len = encode_normal(0, stream_len, setup); + if (stream_len <= 0) + return stream_len; - for (i = 0; i < samples; i++) { - uint16_t round_input = (uint16_t)round_fwd(data_buf[i], round); - uint16_t round_model = (uint16_t)round_fwd(model_buf[i], round); - /* possible underflow is intended */ - data_buf[i] = round_input - round_model; /* TDOO: check if this is the right order */ - /* round back input because for decompression the accurate data - * are not available - */ - up_model_buf[i] = (uint16_t)cal_up_model(round_inv(round_input, round), - model_buf[i], model_value); - } - return 0; + /* put the data unencoded in the bitstream */ + stream_len = put_n_bits32(data, setup->max_data_bits, stream_len, + setup->bitstream_adr, setup->max_stream_len); + + return stream_len; } /** - * @brief model pre-processing and round_input of a uint32_t data buffer + * @brief subtract the model from the data, encode the result and put it into + * bitstream, for encoding outlier use the multi escape symbol mechanism * - * @note overwrite the data_buf in-place with the result - * @note update the model_buf in-place if up_model_buf = NULL + * @param data data to encode + * @param model model of the data (0 if not used) + * @param stream_len length of the bitstream in bits + * @param setup pointer to the encoder setup * - * @param data_buf pointer to the uint32_t data buffer to process - * @param model_buf pointer to the model buffer of the data to process - * @param samples amount of data samples in the data_buf and model_buf buffer - * @param model_value model weighting parameter - * @param round number of bits to round; if zero no rounding takes place + * @returns the bit length of the bitstream with the added encoded value on + * success; negative on error, CMP_ERROR_SAMLL_BUF if the bitstream buffer + * is too small to put the value in the bitstream * - * @returns 0 on success, error otherwise + * @note no check if the data or model are in the allowed range + * @note no check if the setup->spillover_par is in the allowed range */ -static int model_32(uint32_t *data_buf, uint32_t *model_buf, unsigned int samples, - unsigned int model_value, unsigned int round) +static int encode_value_multi(uint32_t data, uint32_t model, int stream_len, + const struct encoder_setupt *setup) { - size_t i; + uint32_t unencoded_data; + unsigned int unencoded_data_len; + uint32_t escape_sym, escape_sym_offset; - if (!data_buf) - return -1; + data -= model; /* possible underflow is intended */ - if (!model_buf) - return -1; + data = map_to_pos(data, setup->max_data_bits); - if (model_value > MAX_MODEL_VALUE) - return -1; + if (data < setup->spillover_par) /* detect non-outlier */ + return encode_normal(data, stream_len, setup); - for (i = 0; i < samples; i++) { - uint32_t round_input = round_fwd(data_buf[i], round); - uint32_t round_model = round_fwd(model_buf[i], round); - /* possible underflow is intended */ - data_buf[i] = round_input - round_model; - /* round back input because for decompression the accurate data - * are not available - */ - model_buf[i] = cal_up_model(round_inv(round_input, round), - model_buf[i], model_value); - } - return 0; + /* + * In this mode we put the difference between the data and the spillover + * threshold value (unencoded_data) after an encoded escape symbol, which + * indicate that the next codeword is unencoded. + * We use different escape symbol depended on the size the needed bit of + * unencoded data: + * 0, 1, 2 bits needed for unencoded data -> escape symbol is spillover_par + 0 + * 3, 4 bits needed for unencoded data -> escape symbol is spillover_par + 1 + * 5, 6 bits needed for unencoded data -> escape symbol is spillover_par + 2 + * and so on + */ + unencoded_data = data - setup->spillover_par; + + if (!unencoded_data) /* catch __builtin_clz(0) because the result is undefined.*/ + escape_sym_offset = 0; + else + escape_sym_offset = (31U - (uint32_t)__builtin_clz(unencoded_data)) >> 1; + + escape_sym = setup->spillover_par + escape_sym_offset; + unencoded_data_len = (escape_sym_offset + 1U) << 1; + + /* put the escape symbol in the bitstream */ + stream_len = encode_normal(escape_sym, stream_len, setup); + if (stream_len <= 0) + return stream_len; + + /* put the unencoded data in the bitstream */ + stream_len = put_n_bits32(unencoded_data, unencoded_data_len, stream_len, + setup->bitstream_adr, setup->max_stream_len); + + return stream_len; } /** - * @brief model pre-processing and round_input of a S_FX data buffer + * @brief put the value unencoded with(setup->cmp_par_1 bits without any changes + * in the bitstream * - * @note overwrite the data_buf in-place with the result - * @note update the model_buf in-place if up_model_buf = NULL + * @param value value to put unchanged in the bitstream + * (setup->cmp_par_1 how many bits of the value are used) + * @param unused this parameter is ignored + * @param stream_len length of the bitstream in bits + * @param setup pointer to the encoder setup * - * @param data_buf pointer to the S_FX data buffer to process - * @param model_buf pointer to the updated model buffer (if NULL model_buf - * will be overwrite with the updated model) - * @param samples amount of data samples in the data_buf and model_buf buffer - * @param model_value model weighting parameter - * @param round number of bits to round; if zero no rounding takes place + * @returns the bit length of the bitstream with the added unencoded value on + * success; negative on error, CMP_ERROR_SAMLL_BUF if the bitstream buffer + * is too small to put the value in the bitstream * - * @returns 0 on success, error otherwise */ -int model_S_FX(struct S_FX *data_buf, struct S_FX *model_buf, - struct S_FX *up_model_buf, unsigned int samples, - unsigned int model_value, unsigned int round) +static int encode_value_none(uint32_t value, uint32_t unused, int stream_len, + const struct encoder_setupt *setup) { - size_t i; - - if (!samples) - return 0; + (void)(unused); - if (!model_buf) - return -1; + return put_n_bits32(value, setup->encoder_par1, stream_len, + setup->bitstream_adr, setup->max_stream_len); +} - if (model_value > MAX_MODEL_VALUE) - return -1; - if (!up_model_buf) /* overwrite the model buffer if no up_model_buf is set */ - up_model_buf = model_buf; +/** + * @brief encodes the data with the model and the given setup and put it into + * the bitstream + * + * @param data data to encode + * @param model model of the data (0 if not used) + * @param stream_len length of the bitstream in bits + * @param setup pointer to the encoder setup + * + * @returns the bit length of the bitstream with the added encoded value on + * success; negative on error, CMP_ERROR_SAMLL_BUF if the bitstream buffer + * is too small to put the value in the bitstream, CMP_ERROR_HIGH_VALUE if + * the value or the model is bigger than the max_used_bits parameter allows + */ +static int encode_value(uint32_t data, uint32_t model, int stream_len, + const struct encoder_setupt *setup) +{ + uint32_t mask = ~(0xFFFFFFFFU >> (32-setup->max_data_bits)); - for (i = 0; i < samples; i++) { - struct S_FX round_data = data_buf[i]; - struct S_FX round_model = model_buf[i]; - int err; + /* lossy rounding of the data if lossy_par > 0 */ + data = round_fwd(data, setup->lossy_par); + model = round_fwd(model, setup->lossy_par); - err = lossy_rounding_S_FX(&round_data, 1, round); - if (err) - return err; + if (data & mask || model & mask) { + debug_print("Error: The data or the model of the data are bigger than expected.\n"); + return CMP_ERROR_HIGH_VALUE; + } - err = lossy_rounding_S_FX(&round_model, 1, round); - if (err) - return err; + return setup->encode_method_f(data, model, stream_len, setup); +} - /* possible underflow is intended */ - data_buf[i] = sub_S_FX(round_data, round_model); - /* round back input because for decompression the accurate data - * are not available - */ - err = de_lossy_rounding_S_FX(&round_data, 1, round); - if (err) - return err; - up_model_buf[i] = cal_up_model_S_FX(round_data, model_buf[i], - model_value); - } +/** + * @brief calculate the maximum length of the bitstream/icu_output_buf in bits + * @note we round down to the next 4-byte allied address because we access the + * cmp_buffer in uint32_t words + * + * @param buffer_length length of the icu_output_buf in samples + * @param data_type used compression data type + * + * @returns buffer size in bits + * + */ - return 0; +static uint32_t cmp_buffer_length_to_bits(uint32_t buffer_length, enum cmp_data_type data_type) +{ + return (cmp_cal_size_of_data(buffer_length, data_type) & ~0x3U) * CHAR_BIT; } /** - * @brief data pre-processing to decorrelate the data + * @brief configure an encoder setup structure to have a setup to encode a vale * - * @param cfg configuration contains all parameters required for compression + * @param setup pointer to the encoder setup + * @param cmp_par compression parameter + * @param spillover spillover_par parameter + * @param lossy_par lossy compression parameter + * @param max_data_bits how many bits are needed to represent the highest possible value + * @param cfg pointer to the compression configuration structure * - * @returns 0 on success, error otherwise + * @returns 0 on success; otherwise error */ -int cmp_pre_process(struct cmp_cfg *cfg) +static int configure_encoder_setup(struct encoder_setupt *setup, + uint32_t cmp_par, uint32_t spillover, + uint32_t lossy_par, uint32_t max_data_bits, + const struct cmp_cfg *cfg) { - if (!cfg) + if (!setup) return -1; - if (cfg->samples == 0) - return 0; + if (!cfg) + return -1; - if (!cfg->input_buf) + setup->encoder_par1 = cmp_par; + setup->spillover_par = spillover; + if (max_data_bits > 32) { + debug_print("Error: max_data_bits parameter is bigger than 32 bits.\n"); return -1; + } + setup->max_data_bits = max_data_bits; + setup->lossy_par = lossy_par; switch (cfg->cmp_mode) { - case MODE_RAW: - case MODE_RAW_S_FX: - return 0; /* in raw mode no pre-processing is necessary */ - break; - case MODE_MODEL_ZERO: - case MODE_MODEL_MULTI: - return model_16((uint16_t *)cfg->input_buf, (uint16_t *)cfg->model_buf, - (uint16_t *)cfg->icu_new_model_buf, cfg->samples, - cfg->model_value, cfg->round); - break; - case MODE_DIFF_ZERO: - case MODE_DIFF_MULTI: - return diff_16((uint16_t *)cfg->input_buf, cfg->samples, - cfg->round); - break; - case MODE_MODEL_ZERO_S_FX: - case MODE_MODEL_MULTI_S_FX: - return model_S_FX((struct S_FX *)cfg->input_buf, (struct S_FX *)cfg->model_buf, - (struct S_FX *)cfg->icu_new_model_buf, cfg->samples, - cfg->model_value, cfg->round); - break; - case MODE_DIFF_ZERO_S_FX: - case MODE_DIFF_MULTI_S_FX: - return diff_S_FX((struct S_FX *)cfg->input_buf, cfg->samples, cfg->round); - break; - case MODE_DIFF_ZERO_S_FX_EFX: - case MODE_DIFF_MULTI_S_FX_EFX: - return diff_S_FX_EFX((struct S_FX_EFX *)cfg->input_buf, - cfg->samples, cfg->round); - break; - case MODE_DIFF_ZERO_S_FX_NCOB: - case MODE_DIFF_MULTI_S_FX_NCOB: - return diff_S_FX_NCOB((struct S_FX_NCOB *)cfg->input_buf, - cfg->samples, cfg->round); + case CMP_MODE_MODEL_ZERO: + case CMP_MODE_DIFF_ZERO: + setup->encode_method_f = &encode_value_zero; + if (ilog_2(cmp_par) < 0) + return -1; + setup->encoder_par2 = (uint32_t)ilog_2(cmp_par); break; - case MODE_DIFF_ZERO_S_FX_EFX_NCOB_ECOB: - case MODE_DIFF_MULTI_S_FX_EFX_NCOB_ECOB: - return diff_S_FX_EFX_NCOB_ECOB((struct S_FX_EFX_NCOB_ECOB *)cfg->input_buf, - cfg->samples, cfg->round); + case CMP_MODE_MODEL_MULTI: + case CMP_MODE_DIFF_MULTI: + setup->encode_method_f = &encode_value_multi; + if (ilog_2(cmp_par) < 0) + return -1; + setup->encoder_par2 = (uint32_t)ilog_2(cmp_par); break; - case MODE_MODEL_ZERO_32: - case MODE_MODEL_MULTI_32: - case MODE_MODEL_ZERO_F_FX: - case MODE_MODEL_MULTI_F_FX: - return model_32((uint32_t *)cfg->input_buf, (uint32_t *)cfg->model_buf, - cfg->samples, cfg->model_value, cfg->round); - break; - case MODE_DIFF_ZERO_32: - case MODE_DIFF_MULTI_32: - case MODE_DIFF_ZERO_F_FX: - case MODE_DIFF_MULTI_F_FX: - return diff_32((uint32_t *)cfg->input_buf, cfg->samples, cfg->round); + case CMP_MODE_STUFF: + setup->encode_method_f = &encode_value_none; + setup->max_data_bits = cmp_par; break; default: - debug_print("Error: Compression mode not supported.\n"); + return -1; } - return -1; -} - - -static uint8_t map_to_pos_alg_8(int8_t value_to_map) -{ - if (value_to_map < 0) - /* NOTE: possible integer overflow is intended */ - return (uint8_t)((-value_to_map) * 2 - 1); - else - /* NOTE: possible integer overflow is intended */ - return (uint8_t)(value_to_map * 2); -} - - -static uint16_t map_to_pos_alg_16(int16_t value_to_map) -{ - if (value_to_map < 0) - /* NOTE: possible integer overflow is intended */ - return (uint16_t)((-value_to_map) * 2 - 1); + /* for encoder_par1 which are a power of two we can use the faster rice_encoder */ + if (is_a_pow_of_2(setup->encoder_par1)) + setup->generate_cw_f = &rice_encoder; else - /* NOTE: possible integer overflow is intended */ - return (uint16_t)(value_to_map * 2); -} + setup->generate_cw_f = &golomb_encoder; + setup->bitstream_adr = cfg->icu_output_buf; + setup->max_stream_len = cmp_buffer_length_to_bits(cfg->buffer_length, cfg->data_type); -static uint32_t map_to_pos_alg_32(int32_t value_to_map) -{ - if (value_to_map < 0) - /* NOTE: possible integer overflow is intended */ - return (uint32_t)((-value_to_map) * 2 - 1); - else - /* NOTE: possible integer overflow is intended */ - return (uint32_t)(value_to_map * 2); + return 0; } /** - * @brief map the signed output of the pre-processing stage to a unsigned value - * range for a 16 bit buffer - * - * @note overwrite the data_buf in-place with the result + * @brief compress imagette data * - * @param data_buf pointer to the uint16_t data buffer to process - * @param samples amount of data samples in the data_buf - * @param zero_mode_used needs to be set if the zero escape symbol mechanism is used + * @param cfg pointer to the compression configuration structure * - * @returns 0 on success, error otherwise + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream, CMP_ERROR_HIGH_VALUE if the value or the model is + * bigger than the max_used_bits parameter allows */ -static int map_to_pos_16(uint16_t *data_buf, uint32_t samples, int zero_mode_used) +static int compress_imagette(const struct cmp_cfg *cfg) { + int err; + int stream_len = 0; size_t i; + struct encoder_setupt setup; + + uint16_t *data_buf = cfg->input_buf; + uint16_t *model_buf = cfg->model_buf; + uint16_t model = 0; + uint16_t *next_model_p = data_buf; + uint16_t *up_model_buf = NULL; + + if (cfg->samples == 0) + return 0; + + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + up_model_buf = cfg->icu_new_model_buf; + } - if (!data_buf) + err = configure_encoder_setup(&setup, cfg->golomb_par, cfg->spill, + cfg->round, max_used_bits.nc_imagette, cfg); + if (err) return -1; - for (i = 0; i < samples; i++) { - data_buf[i] = map_to_pos_alg_16(data_buf[i]); - if (zero_mode_used) - data_buf[i] += 1; + for (i = 0;; i++) { + stream_len = encode_value(data_buf[i], model, stream_len, &setup); + if (stream_len <= 0) + break; + + if (up_model_buf) + up_model_buf[i] = cmp_up_model(data_buf[i], model, cfg->model_value, + setup.lossy_par); + if (i >= cfg->samples-1) + break; + + model = next_model_p[i]; } - return 0; + return stream_len; } /** - * @brief map the signed output of the pre-processing stage to a unsigned value - * range for a 32 bit buffer + * @brief compress the multi-entry packet header structure and sets the data, + * model and up_model pointers to the data after the header * - * @note overwrite the data_buf in-place with the result + * @param data pointer to a pointer pointing to the data to be compressed + * @param model pointer to a pointer pointing to the model of the data + * @param up_model pointer to a pointer pointing to the updated model buffer + * @param cfg pointer to the compression configuration structure * - * @param data_buf pointer to the uint32_t data buffer to process - * @param samples amount of data samples in the data_buf - * @param zero_mode_used needs to be set if the zero escape symbol mechanism is used + * @returns the bit length of the bitstream on success; negative on error, * - * @returns 0 on success, error otherwise + * @note the (void **) cast relies on all pointer types having the same internal + * representation which is common, but not universal; http://www.c-faq.com/ptrs/genericpp.html */ -static int map_to_pos_32(uint32_t *data_buf, uint32_t samples, int - zero_mode_used) +static int compress_multi_entry_hdr(void **data, void **model, void **up_model, + const struct cmp_cfg *cfg) { - size_t i; - - if (!data_buf) + if (cfg->buffer_length < 1) return -1; - for (i = 0; i < samples; i++) { - data_buf[i] = map_to_pos_alg_32(data_buf[i]); - if (zero_mode_used) - data_buf[i] += 1; + if (*data) { + if (cfg->icu_output_buf) + memcpy(cfg->icu_output_buf, *data, MULTI_ENTRY_HDR_SIZE); + *data = (uint8_t *)*data + MULTI_ENTRY_HDR_SIZE; } - return 0; + + if (*model) + *model = (uint8_t *)*model + MULTI_ENTRY_HDR_SIZE; + + if (*up_model) { + if (*data) + memcpy(*up_model, *data, MULTI_ENTRY_HDR_SIZE); + *up_model = (uint8_t *)*up_model + MULTI_ENTRY_HDR_SIZE; + } + + return MULTI_ENTRY_HDR_SIZE * CHAR_BIT; } /** - * @brief map the signed output of the pre-processing stage to a unsigned value - * range for a S_FX buffer - * - * @note overwrite the data_buf in-place with the result + * @brief compress short normal light flux (S_FX) data * - * @param data_buf pointer to the S_FX data buffer to process - * @param samples amount of data samples in the data_buf - * @param zero_mode_used needs to be set if the zero escape symbol mechanism is used + * @param cfg pointer to the compression configuration structure * - * @returns 0 on success, error otherwise + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream */ -int map_to_pos_S_FX(struct S_FX *data_buf, uint32_t samples, int - zero_mode_used) +static int compress_s_fx(const struct cmp_cfg *cfg) { + int err; + int stream_len = 0; size_t i; - if (!data_buf) - return -1; + struct s_fx *data_buf = cfg->input_buf; + struct s_fx *model_buf = cfg->model_buf; + struct s_fx *up_model_buf = NULL; + struct s_fx *next_model_p; + struct s_fx model; + struct encoder_setupt setup_exp_flag, setup_fx; + + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; + + stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); - for (i = 0; i < samples; i++) { - data_buf[i].EXPOSURE_FLAGS = - map_to_pos_alg_8(data_buf[i].EXPOSURE_FLAGS); - data_buf[i].FX = map_to_pos_alg_32(data_buf[i].FX); + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } + + err = configure_encoder_setup(&setup_exp_flag, cfg->cmp_par_exp_flags, cfg->spill_exp_flags, + cfg->round, max_used_bits.s_exp_flags, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_fx, cfg->cmp_par_fx, cfg->spill_fx, + cfg->round, max_used_bits.s_fx, cfg); + if (err) + return -1; - if (zero_mode_used) { - /* data_buf[i].EXPOSURE_FLAGS += 1; */ - data_buf[i].FX += 1; + for (i = 0;; i++) { + stream_len = encode_value(data_buf[i].exp_flags, model.exp_flags, + stream_len, &setup_exp_flag); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].fx, model.fx, stream_len, + &setup_fx); + if (stream_len <= 0) + return stream_len; + + if (up_model_buf) { + up_model_buf[i].exp_flags = cmp_up_model(data_buf[i].exp_flags, model.exp_flags, + cfg->model_value, setup_exp_flag.lossy_par); + up_model_buf[i].fx = cmp_up_model(data_buf[i].fx, model.fx, + cfg->model_value, setup_fx.lossy_par); } + + if (i >= cfg->samples-1) + break; + + model = next_model_p[i]; } - return 0; + return stream_len; } /** - * @brief map the signed output of the pre-processing stage to a unsigned value - * range for a S_FX_EFX buffer - * - * @note overwrite the data_buf in-place with the result + * @brief compress S_FX_EFX data * - * @param data_buf pointer to the S_FX_EFX data buffer to process - * @param samples amount of data samples in the data_buf - * @param zero_mode_used needs to be set if the zero escape symbol mechanism is used + * @param cfg pointer to the compression configuration structure * - * @returns 0 on success, error otherwise + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream */ -static int map_to_pos_S_FX_EFX(struct S_FX_EFX *data_buf, uint32_t samples, int - zero_mode_used) +static int compress_s_fx_efx(const struct cmp_cfg *cfg) { + int err; + int stream_len = 0; size_t i; - if (!data_buf) - return -1; + struct s_fx_efx *data_buf = cfg->input_buf; + struct s_fx_efx *model_buf = cfg->model_buf; + struct s_fx_efx *up_model_buf = NULL; + struct s_fx_efx *next_model_p; + struct s_fx_efx model; + struct encoder_setupt setup_exp_flag, setup_fx, setup_efx; + + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; + + stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); - for (i = 0; i < samples; i++) { - data_buf[i].EXPOSURE_FLAGS = - map_to_pos_alg_8(data_buf[i].EXPOSURE_FLAGS); - data_buf[i].FX = map_to_pos_alg_32(data_buf[i].FX); - data_buf[i].EFX = map_to_pos_alg_32(data_buf[i].EFX); + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } + + err = configure_encoder_setup(&setup_exp_flag, cfg->cmp_par_exp_flags, cfg->spill_exp_flags, + cfg->round, max_used_bits.s_exp_flags, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_fx, cfg->cmp_par_fx, cfg->spill_fx, + cfg->round, max_used_bits.s_fx, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_efx, cfg->cmp_par_efx, cfg->spill_efx, + cfg->round, max_used_bits.s_efx, cfg); + if (err) + return -1; - if (zero_mode_used) { - /* data_buf[i].EXPOSURE_FLAGS += 1; */ - data_buf[i].FX += 1; - data_buf[i].EFX += 1; + for (i = 0;; i++) { + stream_len = encode_value(data_buf[i].exp_flags, model.exp_flags, + stream_len, &setup_exp_flag); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].fx, model.fx, stream_len, + &setup_fx); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].efx, model.efx, + stream_len, &setup_efx); + if (stream_len <= 0) + return stream_len; + + if (up_model_buf) { + up_model_buf[i].exp_flags = cmp_up_model(data_buf[i].exp_flags, model.exp_flags, + cfg->model_value, setup_exp_flag.lossy_par); + up_model_buf[i].fx = cmp_up_model(data_buf[i].fx, model.fx, + cfg->model_value, setup_fx.lossy_par); + up_model_buf[i].efx = cmp_up_model(data_buf[i].efx, model.efx, + cfg->model_value, setup_efx.lossy_par); } + + if (i >= cfg->samples-1) + break; + + model = next_model_p[i]; } - return 0; + return stream_len; } /** - * @brief map the signed output of the pre-processing stage to a unsigned value - * range for a S_FX_NCOB buffer - * - * @note overwrite the data_buf in-place with the result + * @brief compress S_FX_NCOB data * - * @param data_buf pointer to the S_FX_NCOB data buffer to process - * @param samples amount of data samples in the data_buf - * @param zero_mode_used needs to be set if the zero escape symbol mechanism is used + * @param cfg pointer to the compression configuration structure * - * @returns 0 on success, error otherwise + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream */ -static int map_to_pos_S_FX_NCOB(struct S_FX_NCOB *data_buf, uint32_t samples, - int zero_mode_used) +static int compress_s_fx_ncob(const struct cmp_cfg *cfg) { + int err; + int stream_len = 0; size_t i; - if (!data_buf) - return -1; + struct s_fx_ncob *data_buf = cfg->input_buf; + struct s_fx_ncob *model_buf = cfg->model_buf; + struct s_fx_ncob *up_model_buf = NULL; + struct s_fx_ncob *next_model_p; + struct s_fx_ncob model; + struct encoder_setupt setup_exp_flag, setup_fx, setup_ncob; - for (i = 0; i < samples; i++) { - data_buf[i].EXPOSURE_FLAGS = - map_to_pos_alg_8(data_buf[i].EXPOSURE_FLAGS); - data_buf[i].FX = map_to_pos_alg_32(data_buf[i].FX); - data_buf[i].NCOB_X = map_to_pos_alg_32(data_buf[i].NCOB_X); - data_buf[i].NCOB_Y = map_to_pos_alg_32(data_buf[i].NCOB_Y); + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; + + stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); + + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } + + err = configure_encoder_setup(&setup_exp_flag, cfg->cmp_par_exp_flags, cfg->spill_exp_flags, + cfg->round, max_used_bits.s_exp_flags, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_fx, cfg->cmp_par_fx, cfg->spill_fx, + cfg->round, max_used_bits.s_fx, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_ncob, cfg->cmp_par_ncob, cfg->spill_ncob, + cfg->round, max_used_bits.s_ncob, cfg); + if (err) + return -1; - if (zero_mode_used) { - /* data_buf[i].EXPOSURE_FLAGS += 1; */ - data_buf[i].FX += 1; - data_buf[i].NCOB_X += 1; - data_buf[i].NCOB_Y += 1; + for (i = 0;; i++) { + stream_len = encode_value(data_buf[i].exp_flags, model.exp_flags, + stream_len, &setup_exp_flag); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].fx, model.fx, stream_len, + &setup_fx); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].ncob_x, model.ncob_x, + stream_len, &setup_ncob); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].ncob_y, model.ncob_y, + stream_len, &setup_ncob); + if (stream_len <= 0) + return stream_len; + + if (up_model_buf) { + up_model_buf[i].exp_flags = cmp_up_model(data_buf[i].exp_flags, model.exp_flags, + cfg->model_value, setup_exp_flag.lossy_par); + up_model_buf[i].fx = cmp_up_model(data_buf[i].fx, model.fx, + cfg->model_value, setup_fx.lossy_par); + up_model_buf[i].ncob_x = cmp_up_model(data_buf[i].ncob_x, model.ncob_x, + cfg->model_value, setup_ncob.lossy_par); + up_model_buf[i].ncob_y = cmp_up_model(data_buf[i].ncob_y, model.ncob_y, + cfg->model_value, setup_ncob.lossy_par); } + + if (i >= cfg->samples-1) + break; + + model = next_model_p[i]; } - return 0; + return stream_len; } /** - * @brief map the signed output of the pre-processing stage to a unsigned value - * range for a S_FX_EFX_NCOB_ECOB buffer - * - * @note overwrite the data_buf in-place with the result + * @brief compress S_FX_EFX_NCOB_ECOB data * - * @param data_buf pointer to the S_FX_EFX_NCOB_ECOB data buffer to process - * @param samples amount of data samples in the data_buf - * @param zero_mode_used needs to be set if the zero escape symbol mechanism is used + * @param cfg pointer to the compression configuration structure * - * @returns 0 on success, error otherwise + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream */ -static int map_to_pos_S_FX_EFX_NCOB_ECOB(struct S_FX_EFX_NCOB_ECOB *data_buf, - uint32_t samples, int zero_mode_used) +static int compress_s_fx_efx_ncob_ecob(const struct cmp_cfg *cfg) { + int err; + int stream_len = 0; size_t i; - if (!data_buf) - return -1; - - for (i = 0; i < samples; i++) { - data_buf[i].EXPOSURE_FLAGS = - map_to_pos_alg_8(data_buf[i].EXPOSURE_FLAGS); - data_buf[i].FX = map_to_pos_alg_32(data_buf[i].FX); - data_buf[i].NCOB_X = map_to_pos_alg_32(data_buf[i].NCOB_X); - data_buf[i].NCOB_Y = map_to_pos_alg_32(data_buf[i].NCOB_Y); - data_buf[i].EFX = map_to_pos_alg_32(data_buf[i].EFX); - data_buf[i].ECOB_X = map_to_pos_alg_32(data_buf[i].ECOB_X); - data_buf[i].ECOB_Y = map_to_pos_alg_32(data_buf[i].ECOB_Y); - - if (zero_mode_used) { - /* data_buf[i].EXPOSURE_FLAGS += 1; */ - data_buf[i].FX += 1; - data_buf[i].NCOB_X += 1; - data_buf[i].NCOB_Y += 1; - data_buf[i].EFX += 1; - data_buf[i].ECOB_X += 1; - data_buf[i].ECOB_Y += 1; + struct s_fx_efx_ncob_ecob *data_buf = cfg->input_buf; + struct s_fx_efx_ncob_ecob *model_buf = cfg->model_buf; + struct s_fx_efx_ncob_ecob *up_model_buf = NULL; + struct s_fx_efx_ncob_ecob *next_model_p; + struct s_fx_efx_ncob_ecob model; + struct encoder_setupt setup_exp_flag, setup_fx, setup_ncob, setup_efx, + setup_ecob; + + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; + + stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); + + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } + + err = configure_encoder_setup(&setup_exp_flag, cfg->cmp_par_exp_flags, cfg->spill_exp_flags, + cfg->round, max_used_bits.s_exp_flags, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_fx, cfg->cmp_par_fx, cfg->spill_fx, + cfg->round, max_used_bits.s_fx, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_ncob, cfg->cmp_par_ncob, cfg->spill_ncob, + cfg->round, max_used_bits.s_ncob, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_efx, cfg->cmp_par_efx, cfg->spill_efx, + cfg->round, max_used_bits.s_efx, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_ecob, cfg->cmp_par_ecob, cfg->spill_ecob, + cfg->round, max_used_bits.s_ecob, cfg); + if (err) + return -1; + + for (i = 0;; i++) { + stream_len = encode_value(data_buf[i].exp_flags, model.exp_flags, + stream_len, &setup_exp_flag); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].fx, model.fx, stream_len, + &setup_fx); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].ncob_x, model.ncob_x, + stream_len, &setup_ncob); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].ncob_y, model.ncob_y, + stream_len, &setup_ncob); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].efx, model.efx, + stream_len, &setup_efx); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].ecob_x, model.ecob_x, + stream_len, &setup_ecob); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].ecob_y, model.ecob_y, + stream_len, &setup_ecob); + if (stream_len <= 0) + return stream_len; + + if (up_model_buf) { + up_model_buf[i].exp_flags = cmp_up_model(data_buf[i].exp_flags, model.exp_flags, + cfg->model_value, setup_exp_flag.lossy_par); + up_model_buf[i].fx = cmp_up_model(data_buf[i].fx, model.fx, + cfg->model_value, setup_fx.lossy_par); + up_model_buf[i].ncob_x = cmp_up_model(data_buf[i].ncob_x, model.ncob_x, + cfg->model_value, setup_ncob.lossy_par); + up_model_buf[i].ncob_y = cmp_up_model(data_buf[i].ncob_y, model.ncob_y, + cfg->model_value, setup_ncob.lossy_par); + up_model_buf[i].efx = cmp_up_model(data_buf[i].efx, model.efx, + cfg->model_value, setup_efx.lossy_par); + up_model_buf[i].ecob_x = cmp_up_model(data_buf[i].ecob_x, model.ecob_x, + cfg->model_value, setup_ecob.lossy_par); + up_model_buf[i].ecob_y = cmp_up_model(data_buf[i].ecob_y, model.ecob_y, + cfg->model_value, setup_ecob.lossy_par); } + + if (i >= cfg->samples-1) + break; + + model = next_model_p[i]; } - return 0; + return stream_len; } /** - * @brief map the signed output of the pre-processing stage to a unsigned value - * range + * @brief compress F_FX data * - * @note change the data_buf in-place + * @param cfg pointer to the compression configuration structure * - * @param cfg configuration contains all parameters required for compression - * - * @returns 0 on success, error otherwise + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream */ -int cmp_map_to_pos(struct cmp_cfg *cfg) +static int compress_f_fx(const struct cmp_cfg *cfg) { - int zero_mode_used; + int err; + int stream_len = 0; + size_t i; - if (!cfg) - return -1; + struct f_fx *data_buf = cfg->input_buf; + struct f_fx *model_buf = cfg->model_buf; + struct f_fx *up_model_buf = NULL; + struct f_fx *next_model_p; + struct f_fx model; + struct encoder_setupt setup_fx; - if (cfg->samples == 0) - return 0; + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; + + stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); + + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } - if (!cfg->input_buf) + err = configure_encoder_setup(&setup_fx, cfg->cmp_par_fx, cfg->spill_fx, + cfg->round, max_used_bits.f_fx, cfg); + if (err) return -1; - zero_mode_used = zero_escape_mech_is_used(cfg->cmp_mode); + for (i = 0;; i++) { + stream_len = encode_value(data_buf[i].fx, model.fx, stream_len, + &setup_fx); + if (stream_len <= 0) + return stream_len; - switch (cfg->cmp_mode) { - case MODE_RAW: - case MODE_RAW_S_FX: - return 0; /* in raw mode no mapping is necessary */ - break; - case MODE_MODEL_ZERO: - case MODE_MODEL_MULTI: - case MODE_DIFF_ZERO: - case MODE_DIFF_MULTI: - return map_to_pos_16((uint16_t *)cfg->input_buf, cfg->samples, - zero_mode_used); - break; - case MODE_MODEL_ZERO_S_FX: - case MODE_MODEL_MULTI_S_FX: - case MODE_DIFF_ZERO_S_FX: - case MODE_DIFF_MULTI_S_FX: - return map_to_pos_S_FX((struct S_FX *)cfg->input_buf, - cfg->samples, zero_mode_used); - break; - case MODE_MODEL_ZERO_S_FX_EFX: - case MODE_MODEL_MULTI_S_FX_EFX: - case MODE_DIFF_ZERO_S_FX_EFX: - case MODE_DIFF_MULTI_S_FX_EFX: - return map_to_pos_S_FX_EFX((struct S_FX_EFX *)cfg->input_buf, - cfg->samples, zero_mode_used); - break; - case MODE_MODEL_ZERO_S_FX_NCOB: - case MODE_MODEL_MULTI_S_FX_NCOB: - case MODE_DIFF_ZERO_S_FX_NCOB: - case MODE_DIFF_MULTI_S_FX_NCOB: - return map_to_pos_S_FX_NCOB((struct S_FX_NCOB *)cfg->input_buf, - cfg->samples, zero_mode_used); - break; - case MODE_MODEL_ZERO_S_FX_EFX_NCOB_ECOB: - case MODE_MODEL_MULTI_S_FX_EFX_NCOB_ECOB: - case MODE_DIFF_ZERO_S_FX_EFX_NCOB_ECOB: - case MODE_DIFF_MULTI_S_FX_EFX_NCOB_ECOB: - return map_to_pos_S_FX_EFX_NCOB_ECOB((struct S_FX_EFX_NCOB_ECOB *)cfg->input_buf, - cfg->samples, zero_mode_used); - break; - case MODE_MODEL_ZERO_32: - case MODE_MODEL_MULTI_32: - case MODE_DIFF_ZERO_32: - case MODE_DIFF_MULTI_32: - case MODE_MODEL_ZERO_F_FX: - case MODE_MODEL_MULTI_F_FX: - case MODE_DIFF_ZERO_F_FX: - case MODE_DIFF_MULTI_F_FX: - return map_to_pos_32((uint32_t *)cfg->input_buf, cfg->samples, - zero_mode_used); - break; - default: - debug_print("Error: Compression mode not supported.\n"); - break; + if (up_model_buf) { + up_model_buf[i].fx = cmp_up_model(data_buf[i].fx, model.fx, + cfg->model_value, setup_fx.lossy_par); + } - } + if (i >= cfg->samples-1) + break; - return -1; + model = next_model_p[i]; + } + return stream_len; } /** - * @brief forms the codeword accurate to the Rice code and returns its length + * @brief compress F_FX_EFX data * - * @param m Golomb parameter, only m's which are power of 2 and >0 - * are allowed! - * @param log2_m Rice parameter, is log_2(m) calculate outside function - * for better performance - * @param value value to be encoded - * @param cw address were the encode code word is stored + * @param cfg pointer to the compression configuration structure * - * @returns length of the encoded code word in bits + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream */ -static unsigned int Rice_encoder(unsigned int value, unsigned int m, - unsigned int log2_m, unsigned int *cw) +static int compress_f_fx_efx(const struct cmp_cfg *cfg) { - unsigned int g; /* quotient of value/m */ - unsigned int q; /* quotient code without ending zero */ - unsigned int r; /* remainder of value/m */ - unsigned int rl; /* remainder length */ + int err; + int stream_len = 0; + size_t i; - g = value >> log2_m; /* quotient, number of leading bits */ - q = (1U << g) - 1; /* prepare the quotient code without ending zero */ + struct f_fx_efx *data_buf = cfg->input_buf; + struct f_fx_efx *model_buf = cfg->model_buf; + struct f_fx_efx *up_model_buf = NULL; + struct f_fx_efx *next_model_p; + struct f_fx_efx model; + struct encoder_setupt setup_fx, setup_efx; - r = value & (m-1); /* calculate the remainder */ - rl = log2_m + 1; /* length of the remainder (+1 for the 0 in the - * quotient code) - */ - *cw = (q << rl) | r; /* put the quotient and remainder code together */ + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; - return rl + g; /* calculate the length of the code word */ + stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); + + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } + + err = configure_encoder_setup(&setup_fx, cfg->cmp_par_fx, cfg->spill_fx, + cfg->round, max_used_bits.f_fx, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_efx, cfg->cmp_par_efx, cfg->spill_efx, + cfg->round, max_used_bits.f_efx, cfg); + if (err) + return -1; + + for (i = 0;; i++) { + stream_len = encode_value(data_buf[i].fx, model.fx, stream_len, + &setup_fx); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].efx, model.efx, + stream_len, &setup_efx); + if (stream_len <= 0) + return stream_len; + + if (up_model_buf) { + up_model_buf[i].fx = cmp_up_model(data_buf[i].fx, model.fx, + cfg->model_value, setup_fx.lossy_par); + up_model_buf[i].efx = cmp_up_model(data_buf[i].efx, model.efx, + cfg->model_value, setup_efx.lossy_par); + } + + if (i >= cfg->samples-1) + break; + + model = next_model_p[i]; + } + return stream_len; } /** - * @brief forms the codeword accurate to the Golomb code and returns its length + * @brief compress F_FX_NCOB data * - * @param m Golomb parameter, only m's which are power of 2 and >0 - * are allowed! - * @param log2_m is log_2(m) calculate outside function for better - * performance - * @param value value to be encoded - * @param cw address were the encode code word is stored + * @param cfg pointer to the compression configuration structure * - * @returns length of the encoded code word in bits + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream */ -static unsigned int Golomb_encoder(unsigned int value, unsigned int m, - unsigned int log2_m, unsigned int *cw) +static int compress_f_fx_ncob(const struct cmp_cfg *cfg) { - unsigned int len0, b, g, q, lg; - unsigned int len; - unsigned int cutoff; + int err; + int stream_len = 0; + size_t i; - len0 = log2_m + 1; /* codeword length in group 0 */ - cutoff = (1U << (log2_m+1)) - m; /* members in group 0 */ - if (cutoff == 0) /* for powers of two we fix cutoff = m */ - cutoff = m; + struct f_fx_ncob *data_buf = cfg->input_buf; + struct f_fx_ncob *model_buf = cfg->model_buf; + struct f_fx_ncob *up_model_buf = NULL; + struct f_fx_ncob *next_model_p; + struct f_fx_ncob model; + struct encoder_setupt setup_fx, setup_ncob; - if (value < cutoff) { /* group 0 */ - *cw = value; - len = len0; - } else { /* other groups */ - b = (cutoff << 1); /* form the base codeword */ - g = (value-cutoff)/m; /* this group is which one */ - lg = len0 + g; /* it has lg remainder bits */ - q = (1U << g) - 1; /* prepare the left side in unary */ - *cw = (q << (len0+1)) + b + (value-cutoff)-g*m; /* composed codeword */ - len = lg + 1; /* length of the codeword */ + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; + + stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); + + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; } - return len; -} + err = configure_encoder_setup(&setup_fx, cfg->cmp_par_fx, cfg->spill_fx, + cfg->round, max_used_bits.f_fx, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_ncob, cfg->cmp_par_ncob, cfg->spill_ncob, + cfg->round, max_used_bits.f_ncob, cfg); + if (err) + return -1; -typedef unsigned int (*encoder_ptr)(unsigned int, unsigned int, unsigned int, - unsigned int*); + for (i = 0;; i++) { + stream_len = encode_value(data_buf[i].fx, model.fx, stream_len, + &setup_fx); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].ncob_x, model.ncob_x, + stream_len, &setup_ncob); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].ncob_y, model.ncob_y, + stream_len, &setup_ncob); + if (stream_len <= 0) + return stream_len; + + if (up_model_buf) { + up_model_buf[i].fx = cmp_up_model(data_buf[i].fx, model.fx, + cfg->model_value, setup_fx.lossy_par); + up_model_buf[i].ncob_x = cmp_up_model(data_buf[i].ncob_x, model.ncob_x, + cfg->model_value, setup_ncob.lossy_par); + up_model_buf[i].ncob_y = cmp_up_model(data_buf[i].ncob_y, model.ncob_y, + cfg->model_value, setup_ncob.lossy_par); + } -static encoder_ptr select_encoder(unsigned int golomb_par) -{ - if (!golomb_par) - return NULL; + if (i >= cfg->samples-1) + break; - if (is_a_pow_of_2(golomb_par)) - return &Rice_encoder; - else - return &Golomb_encoder; + model = next_model_p[i]; + } + return stream_len; } /** - * @brief safe (but slow) way to put the value of up to 32 bits into a - * bitstream accessed as 32-bit RAM in big endian - * @param value the value to put, it will be masked - * @param bitOffset bit index where the bits will be put, seen from the very - * beginning of the bitstream - * @param nBits number of bits to put in the bitstream - * @param destAddr this is the pointer to the beginning of the bitstream - * @param dest_len length of the bitstream buffer (starting at destAddr) - * @returns TODO number of bits written, 0 if the number was too big, -2 if the - * destAddr buffer is to small to store the bitstream - * @note works in SRAM2 + * @brief compress F_FX_EFX_NCOB_ECOB data + * + * @param cfg pointer to the compression configuration structure + * + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream */ -static unsigned int put_n_bits32(unsigned int value, unsigned int bitOffset, - unsigned int nBits, unsigned int *destAddr, - unsigned int dest_len) +static int compress_f_fx_efx_ncob_ecob(const struct cmp_cfg *cfg) { - unsigned int *localAddr; - unsigned int bitsLeft, shiftRight, shiftLeft, localEndPos; - unsigned int mask; + int err; + int stream_len = 0; + size_t i; - if (!destAddr) - return nBits; + struct f_fx_efx_ncob_ecob *data_buf = cfg->input_buf; + struct f_fx_efx_ncob_ecob *model_buf = cfg->model_buf; + struct f_fx_efx_ncob_ecob *up_model_buf = NULL; + struct f_fx_efx_ncob_ecob *next_model_p; + struct f_fx_efx_ncob_ecob model; + struct encoder_setupt setup_fx, setup_ncob, setup_efx, setup_ecob; - /* check if destination buffer is large enough */ - /* TODO: adapt that to the other science products */ - if ((bitOffset + nBits) > ((dest_len&~0x1U)*16)) { - debug_print("Error: The buffer for the compressed data is too small to hold the compressed data. Try a larger buffer_length parameter.\n"); - return -2U; - } + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; - /* leave in case of erroneous input */ - if (nBits == 0) - return 0; - if (nBits > 32) - return 0; + stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); - /* separate the bitOffset into word offset (set localAddr pointer) and local bit offset (bitsLeft) */ - localAddr = destAddr + (bitOffset >> 5); - bitsLeft = bitOffset & 0x1f; + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } - /* (M) we mask the value first to match its size in nBits */ - /* the calculations can be re-used in the unsegmented code, so we have no overhead */ - shiftRight = 32 - nBits; - mask = 0xffffffff >> shiftRight; - value &= mask; + err = configure_encoder_setup(&setup_fx, cfg->cmp_par_fx, cfg->spill_fx, + cfg->round, max_used_bits.f_fx, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_ncob, cfg->cmp_par_ncob, cfg->spill_ncob, + cfg->round, max_used_bits.f_ncob, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_efx, cfg->cmp_par_efx, cfg->spill_efx, + cfg->round, max_used_bits.f_efx, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_ecob, cfg->cmp_par_ecob, cfg->spill_ecob, + cfg->round, max_used_bits.f_ecob, cfg); + if (err) + return -1; - /* to see if we need to split the value over two words we need the right end position */ - localEndPos = bitsLeft + nBits; + for (i = 0;; i++) { + stream_len = encode_value(data_buf[i].fx, model.fx, stream_len, + &setup_fx); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].ncob_x, model.ncob_x, + stream_len, &setup_ncob); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].ncob_y, model.ncob_y, + stream_len, &setup_ncob); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].efx, model.efx, + stream_len, &setup_efx); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].ecob_x, model.ecob_x, + stream_len, &setup_ecob); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].ecob_y, model.ecob_y, + stream_len, &setup_ecob); + if (stream_len <= 0) + return stream_len; + + if (up_model_buf) { + up_model_buf[i].fx = cmp_up_model(data_buf[i].fx, model.fx, + cfg->model_value, setup_fx.lossy_par); + up_model_buf[i].ncob_x = cmp_up_model(data_buf[i].ncob_x, model.ncob_x, + cfg->model_value, setup_ncob.lossy_par); + up_model_buf[i].ncob_y = cmp_up_model(data_buf[i].ncob_y, model.ncob_y, + cfg->model_value, setup_ncob.lossy_par); + up_model_buf[i].efx = cmp_up_model(data_buf[i].efx, model.efx, + cfg->model_value, setup_efx.lossy_par); + up_model_buf[i].ecob_x = cmp_up_model(data_buf[i].ecob_x, model.ecob_x, + cfg->model_value, setup_ecob.lossy_par); + up_model_buf[i].ecob_y = cmp_up_model(data_buf[i].ecob_y, model.ecob_y, + cfg->model_value, setup_ecob.lossy_par); + } - if (localEndPos <= 32) { - /* UNSEGMENTED - * - *|-----------|XXXXX|----------------| - * bitsLeft n bitsRight - * - * -> to get the mask: - * shiftRight = bitsLeft + bitsRight = 32 - n - * shiftLeft = bitsRight - * - */ + if (i >= cfg->samples-1) + break; - /* shiftRight = 32 - nBits; */ /* see (M) above! */ - shiftLeft = shiftRight - bitsLeft; + model = next_model_p[i]; + } + return stream_len; +} - /* generate the mask, the bits for the values will be true */ - /* mask = (0xffffffff >> shiftRight) << shiftLeft; */ /* see (M) above! */ - mask <<= shiftLeft; - /* clear the destination with inverse mask */ - *(localAddr) &= ~mask; +/** + * @brief compress L_FX data + * + * @param cfg pointer to the compression configuration structure + * + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream + */ - /* assign the value */ - *(localAddr) |= (value << (32 - localEndPos)); /* NOTE: 32-localEndPos = shiftLeft can be simplified */ +static int compress_l_fx(const struct cmp_cfg *cfg) +{ + int err; + int stream_len = 0; + size_t i; - } else { - /* SEGMENTED - * - *|-----------------------------|XXX| |XX|------------------------------| - * bitsLeft n1 n2 bitsRight - * - * -> to get the mask part 1: - * shiftright = bitsleft - * n1 = n - (bitsleft + n - 32) = 32 - bitsleft - * - * -> to get the mask part 2: - * n2 = bitsleft + n - 32 - * shiftleft = 32 - n2 = 32 - (bitsleft + n - 32) = 64 - bitsleft - n - * - */ + struct l_fx *data_buf = cfg->input_buf; + struct l_fx *model_buf = cfg->model_buf; + struct l_fx *up_model_buf = NULL; + struct l_fx *next_model_p; + struct l_fx model; + struct encoder_setupt setup_exp_flag, setup_fx, setup_fx_var; - unsigned int n2 = bitsLeft + nBits - 32; + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; - /* part 1: */ - shiftRight = bitsLeft; - mask = 0xffffffff >> shiftRight; + stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); - /* clear the destination with inverse mask */ - *(localAddr) &= ~mask; + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } - /* assign the value part 1 */ - *(localAddr) |= (value >> n2); + err = configure_encoder_setup(&setup_exp_flag, cfg->cmp_par_exp_flags, cfg->spill_exp_flags, + cfg->round, max_used_bits.l_exp_flags, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_fx, cfg->cmp_par_fx, cfg->spill_fx, + cfg->round, max_used_bits.l_fx, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_fx_var, cfg->cmp_par_fx_cob_variance, cfg->spill_fx_cob_variance, + cfg->round, max_used_bits.l_fx_variance, cfg); + if (err) + return -1; - /* part 2: */ - /* adjust address */ - localAddr += 1; - shiftLeft = 64 - bitsLeft - nBits; - mask = 0xffffffff << shiftLeft; + for (i = 0;; i++) { + stream_len = encode_value(data_buf[i].exp_flags, model.exp_flags, + stream_len, &setup_exp_flag); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].fx, model.fx, stream_len, + &setup_fx); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].fx_variance, model.fx_variance, + stream_len, &setup_fx_var); + if (stream_len <= 0) + return stream_len; + + if (up_model_buf) { + up_model_buf[i].exp_flags = cmp_up_model(data_buf[i].exp_flags, model.exp_flags, + cfg->model_value, setup_exp_flag.lossy_par); + up_model_buf[i].fx = cmp_up_model(data_buf[i].fx, model.fx, + cfg->model_value, setup_fx.lossy_par); + up_model_buf[i].fx_variance = cmp_up_model(data_buf[i].fx_variance, model.fx_variance, + cfg->model_value, setup_fx_var.lossy_par); + } - /* clear the destination with inverse mask */ - *(localAddr) &= ~mask; + if (i >= cfg->samples-1) + break; - /* assign the value part 2 */ - *(localAddr) |= (value << (32 - n2)); + model = next_model_p[i]; } - return nBits; + return stream_len; } -struct encoder_struct { - encoder_ptr encoder; - unsigned int log2_golomb_par; /* pre-calculated for performance increase */ - uint32_t cmp_size; /* Compressed data size; measured in bits */ -}; - +/** + * @brief compress L_FX_EFX data + * + * @param cfg pointer to the compression configuration structure + * + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream + */ -static int encode_raw(struct cmp_cfg *cfg, struct encoder_struct *enc) +static int compress_l_fx_efx(const struct cmp_cfg *cfg) { - size_t cmp_size_in_bytes; - - if (!cfg) - return -1; + int err; + int stream_len = 0; + size_t i; - if (!cfg->icu_output_buf) - return -1; + struct l_fx_efx *data_buf = cfg->input_buf; + struct l_fx_efx *model_buf = cfg->model_buf; + struct l_fx_efx *up_model_buf = NULL; + struct l_fx_efx *next_model_p; + struct l_fx_efx model; + struct encoder_setupt setup_exp_flag, setup_fx, setup_efx, setup_fx_var; - if (!cfg->input_buf) - return -1; + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; - cmp_size_in_bytes = cfg->samples * size_of_a_sample(cfg->cmp_mode); + stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); - enc->cmp_size = cmp_size_in_bytes * CHAR_BIT; /* cmp_size is measured in bits */ + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } - if (cmp_size_in_bytes > cfg->buffer_length * sizeof(uint16_t)) + err = configure_encoder_setup(&setup_exp_flag, cfg->cmp_par_exp_flags, cfg->spill_exp_flags, + cfg->round, max_used_bits.l_exp_flags, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_fx, cfg->cmp_par_fx, cfg->spill_fx, + cfg->round, max_used_bits.l_fx, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_efx, cfg->cmp_par_efx, cfg->spill_efx, + cfg->round, max_used_bits.l_efx, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_fx_var, cfg->cmp_par_fx_cob_variance, cfg->spill_fx_cob_variance, + cfg->round, max_used_bits.l_fx_variance, cfg); + if (err) return -1; - memcpy(cfg->icu_output_buf, cfg->input_buf, cmp_size_in_bytes); + for (i = 0;; i++) { + stream_len = encode_value(data_buf[i].exp_flags, model.exp_flags, + stream_len, &setup_exp_flag); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].fx, model.fx, stream_len, + &setup_fx); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].efx, model.efx, + stream_len, &setup_efx); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].fx_variance, model.fx_variance, + stream_len, &setup_fx_var); + if (stream_len <= 0) + return stream_len; + + if (up_model_buf) { + up_model_buf[i].exp_flags = cmp_up_model(data_buf[i].exp_flags, model.exp_flags, + cfg->model_value, setup_exp_flag.lossy_par); + up_model_buf[i].fx = cmp_up_model(data_buf[i].fx, model.fx, + cfg->model_value, setup_fx.lossy_par); + up_model_buf[i].efx = cmp_up_model(data_buf[i].efx, model.efx, + cfg->model_value, setup_efx.lossy_par); + up_model_buf[i].fx_variance = cmp_up_model(data_buf[i].fx_variance, model.fx_variance, + cfg->model_value, setup_fx_var.lossy_par); + } - return 0; + if (i >= cfg->samples-1) + break; + + model = next_model_p[i]; + } + return stream_len; } -static int encode_raw_16(struct cmp_cfg *cfg, struct encoder_struct *enc) +/** + * @brief compress L_FX_NCOB data + * + * @param cfg pointer to the compression configuration structure + * + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream + */ + +static int compress_l_fx_ncob(const struct cmp_cfg *cfg) { int err; + int stream_len = 0; size_t i; - enc->cmp_size = 0; + struct l_fx_ncob *data_buf = cfg->input_buf; + struct l_fx_ncob *model_buf = cfg->model_buf; + struct l_fx_ncob *up_model_buf = NULL; + struct l_fx_ncob *next_model_p; + struct l_fx_ncob model; + struct encoder_setupt setup_exp_flag, setup_fx, setup_ncob, + setup_fx_var, setup_cob_var; - for (i = 0; i < cfg->samples; i++) { - uint16_t *p = cfg->input_buf; - err = put_n_bits32(p[i], enc->cmp_size, 16, cfg->icu_output_buf, - cfg->buffer_length); - if (err <= 0) - return err; - enc->cmp_size += 16; - } + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; - return 0; -} + stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } -static int encode_raw_S_FX(struct cmp_cfg *cfg, struct encoder_struct *enc) -{ - int err; - - err = encode_raw(cfg, enc); + err = configure_encoder_setup(&setup_exp_flag, cfg->cmp_par_exp_flags, cfg->spill_exp_flags, + cfg->round, max_used_bits.l_exp_flags, cfg); if (err) - return err; + return -1; + err = configure_encoder_setup(&setup_fx, cfg->cmp_par_fx, cfg->spill_fx, + cfg->round, max_used_bits.l_fx, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_ncob, cfg->cmp_par_ncob, cfg->spill_ncob, + cfg->round, max_used_bits.l_ncob, cfg); + if (err) + return -1; + /* we use compression parameter for both variance data fields */ + err = configure_encoder_setup(&setup_fx_var, cfg->cmp_par_fx_cob_variance, cfg->spill_fx_cob_variance, + cfg->round, max_used_bits.l_fx_variance, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_cob_var, cfg->cmp_par_fx_cob_variance, cfg->spill_fx_cob_variance, + cfg->round, max_used_bits.l_cob_variance, cfg); + if (err) + return -1; -#if defined(__LITTLE_ENDIAN) - { - size_t i; - for (i = 0; i < cfg->samples; i++) { - struct S_FX *output_buf = (void *)cfg->icu_output_buf; - output_buf[i].FX = cpu_to_be32(output_buf[i].FX); + for (i = 0;; i++) { + stream_len = encode_value(data_buf[i].exp_flags, model.exp_flags, + stream_len, &setup_exp_flag); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].fx, model.fx, stream_len, + &setup_fx); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].ncob_x, model.ncob_x, + stream_len, &setup_ncob); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].ncob_y, model.ncob_y, + stream_len, &setup_ncob); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].fx_variance, model.fx_variance, + stream_len, &setup_fx_var); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].cob_x_variance, model.cob_x_variance, + stream_len, &setup_cob_var); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].cob_y_variance, model.cob_y_variance, + stream_len, &setup_cob_var); + if (stream_len <= 0) + return stream_len; + + if (up_model_buf) { + up_model_buf[i].exp_flags = cmp_up_model(data_buf[i].exp_flags, model.exp_flags, + cfg->model_value, setup_exp_flag.lossy_par); + up_model_buf[i].fx = cmp_up_model(data_buf[i].fx, model.fx, + cfg->model_value, setup_fx.lossy_par); + up_model_buf[i].ncob_x = cmp_up_model(data_buf[i].ncob_x, model.ncob_x, + cfg->model_value, setup_ncob.lossy_par); + up_model_buf[i].ncob_y = cmp_up_model(data_buf[i].ncob_y, model.ncob_y, + cfg->model_value, setup_ncob.lossy_par); + up_model_buf[i].fx_variance = cmp_up_model(data_buf[i].fx_variance, model.fx_variance, + cfg->model_value, setup_fx_var.lossy_par); + up_model_buf[i].cob_x_variance = cmp_up_model(data_buf[i].cob_x_variance, model.cob_x_variance, + cfg->model_value, setup_cob_var.lossy_par); + up_model_buf[i].cob_y_variance = cmp_up_model(data_buf[i].cob_y_variance, model.cob_y_variance, + cfg->model_value, setup_cob_var.lossy_par); } + + if (i >= cfg->samples-1) + break; + + model = next_model_p[i]; } -#endif - return 0; + return stream_len; } -static int encode_normal(uint32_t value_to_encode, struct cmp_cfg *cfg, - struct encoder_struct *enc) +/** + * @brief compress L_FX_EFX_NCOB_ECOB data + * + * @param cfg pointer to the compression configuration structure + * + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream + */ + +static int compress_l_fx_efx_ncob_ecob(const struct cmp_cfg *cfg) { - unsigned int code_word; - unsigned int cw_len; int err; + int stream_len = 0; + size_t i; - if (!enc->encoder) - return -1; - - cw_len = enc->encoder(value_to_encode, cfg->golomb_par, - enc->log2_golomb_par, &code_word); - - err = put_n_bits32(code_word, enc->cmp_size, cw_len, - cfg->icu_output_buf, cfg->buffer_length); - if (err <= 0) - return err; - - enc->cmp_size += cw_len; + struct l_fx_efx_ncob_ecob *data_buf = cfg->input_buf; + struct l_fx_efx_ncob_ecob *model_buf = cfg->model_buf; + struct l_fx_efx_ncob_ecob *up_model_buf = NULL; + struct l_fx_efx_ncob_ecob *next_model_p; + struct l_fx_efx_ncob_ecob model; + struct encoder_setupt setup_exp_flag, setup_fx, setup_ncob, setup_efx, + setup_ecob, setup_fx_var, setup_cob_var; - return 0; -} + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; + stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); -static int encode_outlier_zero(uint32_t value_to_encode, int max_bits, - struct cmp_cfg *cfg, struct encoder_struct *enc) -{ - int err; + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } - if (max_bits > 32) + err = configure_encoder_setup(&setup_exp_flag, cfg->cmp_par_exp_flags, cfg->spill_exp_flags, + cfg->round, max_used_bits.l_exp_flags, cfg); + if (err) return -1; - - /* use zero as escape symbol */ - err = encode_normal(0, cfg, enc); + err = configure_encoder_setup(&setup_fx, cfg->cmp_par_fx, cfg->spill_fx, + cfg->round, max_used_bits.l_fx, cfg); if (err) - return err; + return -1; + err = configure_encoder_setup(&setup_ncob, cfg->cmp_par_ncob, cfg->spill_ncob, + cfg->round, max_used_bits.l_ncob, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_efx, cfg->cmp_par_efx, cfg->spill_efx, + cfg->round, max_used_bits.l_efx, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_ecob, cfg->cmp_par_ecob, cfg->spill_ecob, + cfg->round, max_used_bits.l_ecob, cfg); + if (err) + return -1; + /* we use compression parameter for both variance data fields */ + err = configure_encoder_setup(&setup_fx_var, cfg->cmp_par_fx_cob_variance, cfg->spill_fx_cob_variance, + cfg->round, max_used_bits.l_fx_variance, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_cob_var, cfg->cmp_par_fx_cob_variance, cfg->spill_fx_cob_variance, + cfg->round, max_used_bits.l_cob_variance, cfg); + if (err) + return -1; - /* put the data unencoded in the bitstream */ - err = put_n_bits32(value_to_encode, enc->cmp_size, max_bits, - cfg->icu_output_buf, cfg->buffer_length); - if (err <= 0) - return err; + for (i = 0;; i++) { + stream_len = encode_value(data_buf[i].exp_flags, model.exp_flags, + stream_len, &setup_exp_flag); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].fx, model.fx, stream_len, + &setup_fx); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].ncob_x, model.ncob_x, + stream_len, &setup_ncob); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].ncob_y, model.ncob_y, + stream_len, &setup_ncob); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].efx, model.efx, + stream_len, &setup_efx); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].ecob_x, model.ecob_x, + stream_len, &setup_ecob); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].ecob_y, model.ecob_y, + stream_len, &setup_ecob); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].fx_variance, model.fx_variance, + stream_len, &setup_fx_var); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].cob_x_variance, model.cob_x_variance, + stream_len, &setup_cob_var); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].cob_y_variance, model.cob_y_variance, + stream_len, &setup_cob_var); + if (stream_len <= 0) + return stream_len; + + if (up_model_buf) { + up_model_buf[i].exp_flags = cmp_up_model(data_buf[i].exp_flags, model.exp_flags, + cfg->model_value, setup_exp_flag.lossy_par); + up_model_buf[i].fx = cmp_up_model(data_buf[i].fx, model.fx, + cfg->model_value, setup_fx.lossy_par); + up_model_buf[i].ncob_x = cmp_up_model(data_buf[i].ncob_x, model.ncob_x, + cfg->model_value, setup_ncob.lossy_par); + up_model_buf[i].ncob_y = cmp_up_model(data_buf[i].ncob_y, model.ncob_y, + cfg->model_value, setup_ncob.lossy_par); + up_model_buf[i].efx = cmp_up_model(data_buf[i].efx, model.efx, + cfg->model_value, setup_efx.lossy_par); + up_model_buf[i].ecob_x = cmp_up_model(data_buf[i].ecob_x, model.ecob_x, + cfg->model_value, setup_ecob.lossy_par); + up_model_buf[i].ecob_y = cmp_up_model(data_buf[i].ecob_y, model.ecob_y, + cfg->model_value, setup_ecob.lossy_par); + up_model_buf[i].fx_variance = cmp_up_model(data_buf[i].fx_variance, model.fx_variance, + cfg->model_value, setup_fx_var.lossy_par); + up_model_buf[i].cob_x_variance = cmp_up_model(data_buf[i].cob_x_variance, model.cob_x_variance, + cfg->model_value, setup_cob_var.lossy_par); + up_model_buf[i].cob_y_variance = cmp_up_model(data_buf[i].cob_y_variance, model.cob_y_variance, + cfg->model_value, setup_cob_var.lossy_par); + } - enc->cmp_size += max_bits; + if (i >= cfg->samples-1) + break; - return 0; + model = next_model_p[i]; + } + return stream_len; } -static int cal_multi_offset(unsigned int unencoded_data) -{ - if (unencoded_data <= 0x3) - return 0; - if (unencoded_data <= 0xF) - return 1; - if (unencoded_data <= 0x3F) - return 2; - if (unencoded_data <= 0xFF) - return 3; - if (unencoded_data <= 0x3FF) - return 4; - if (unencoded_data <= 0xFFF) - return 5; - if (unencoded_data <= 0x3FFF) - return 6; - if (unencoded_data <= 0xFFFF) - return 7; - if (unencoded_data <= 0x3FFFF) - return 8; - if (unencoded_data <= 0xFFFFF) - return 9; - if (unencoded_data <= 0x3FFFFF) - return 10; - if (unencoded_data <= 0xFFFFFF) - return 11; - if (unencoded_data <= 0x3FFFFFF) - return 12; - if (unencoded_data <= 0xFFFFFFF) - return 13; - if (unencoded_data <= 0x3FFFFFFF) - return 14; - else - return 15; -} - +/** + * @brief compress offset data from the normal cameras + * + * @param cfg pointer to the compression configuration structure + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream + */ -static int encode_outlier_multi(uint32_t value_to_encode, struct cmp_cfg *cfg, - struct encoder_struct *enc) +static int compress_nc_offset(const struct cmp_cfg *cfg) { - uint32_t unencoded_data; - unsigned int unencoded_data_len; - uint32_t escape_sym; - int escape_sym_offset; int err; + int stream_len = 0; + size_t i; - /* - * In this mode we put the difference between the data and the spillover - * threshold value (unencoded_data) after a encoded escape symbol, which - * indicate that the next codeword is unencoded. - * We use different escape symbol depended on the size the needed bit of - * unencoded data: - * 0, 1, 2 bits needed for unencoded data -> escape symbol is spill + 0 - * 3, 4 bits needed for unencoded data -> escape symbol is spill + 1 - * .. - */ - - unencoded_data = value_to_encode - cfg->spill; - escape_sym_offset = cal_multi_offset(unencoded_data); - escape_sym = cfg->spill + escape_sym_offset; - unencoded_data_len = (escape_sym_offset + 1) * 2; - - /* put the escape symbol in the bitstream */ - err = encode_normal(escape_sym, cfg, enc); - if (err) - return err; - - /* put the unencoded data in the bitstream */ - err = put_n_bits32(unencoded_data, enc->cmp_size, unencoded_data_len, - cfg->icu_output_buf, cfg->buffer_length); - if (err <= 0) - return err; - - enc->cmp_size += unencoded_data_len; - - return 0; -} - -static int encode_outlier(uint32_t value_to_encode, int bit_len, struct cmp_cfg - *cfg, struct encoder_struct *enc) -{ - if (multi_escape_mech_is_used(cfg->cmp_mode)) - return encode_outlier_multi(value_to_encode, cfg, enc); - - if (zero_escape_mech_is_used(cfg->cmp_mode)) - return encode_outlier_zero(value_to_encode, bit_len, cfg, enc); - - return -1; -} - + struct nc_offset *data_buf = cfg->input_buf; + struct nc_offset *model_buf = cfg->model_buf; + struct nc_offset *up_model_buf = NULL; + struct nc_offset *next_model_p; + struct nc_offset model; + struct encoder_setupt setup_mean, setup_var; -int encode_value(uint32_t value_to_encode, int bit_len, struct cmp_cfg *cfg, - struct encoder_struct *enc) -{ - /* 0 is an outlier in case of a zero-escape mechanism, because an - * overflow can occur by incrementing by one - */ - if (value_to_encode >= cfg->spill || - (zero_escape_mech_is_used(cfg->cmp_mode) && value_to_encode == 0)) - return encode_outlier(value_to_encode, bit_len, cfg, enc); - else - return encode_normal(value_to_encode, cfg, enc); -} + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; + stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); -static int encode_16(struct cmp_cfg *cfg, struct encoder_struct *enc) -{ - size_t i; - uint16_t *data_to_encode; + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } - if (!cfg) + err = configure_encoder_setup(&setup_mean, cfg->cmp_par_mean, cfg->spill_mean, + cfg->round, max_used_bits.nc_offset_mean, cfg); + if (err) return -1; - - if (!enc) + err = configure_encoder_setup(&setup_var, cfg->cmp_par_variance, cfg->spill_variance, + cfg->round, max_used_bits.nc_offset_variance, cfg); + if (err) return -1; - data_to_encode = cfg->input_buf; + for (i = 0;; i++) { + stream_len = encode_value(data_buf[i].mean, model.mean, + stream_len, &setup_mean); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].variance, model.variance, + stream_len, &setup_var); + if (stream_len <= 0) + return stream_len; + + if (up_model_buf) { + up_model_buf[i].mean = cmp_up_model(data_buf[i].mean, model.mean, + cfg->model_value, setup_mean.lossy_par); + up_model_buf[i].variance = cmp_up_model(data_buf[i].variance, model.variance, + cfg->model_value, setup_var.lossy_par); + } + + if (i >= cfg->samples-1) + break; - for (i = 0; i < cfg->samples; i++) { - int err = encode_value(data_to_encode[i], 16, cfg, enc); - if (err) - return err; + model = next_model_p[i]; } - return 0; + return stream_len; } -static int encode_32(struct cmp_cfg *cfg, struct encoder_struct *enc) +/** + * @brief compress background data from the normal cameras + * + * @param cfg pointer to the compression configuration structure + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream + */ + +static int compress_nc_background(const struct cmp_cfg *cfg) { - uint32_t *data_to_encode = cfg->input_buf; + int err; + int stream_len = 0; size_t i; - for (i = 0; i < cfg->samples; i++) { - int err = encode_value(data_to_encode[i], 32, cfg, enc); - if (err) - return err; - } - return 0; -} + struct nc_background *data_buf = cfg->input_buf; + struct nc_background *model_buf = cfg->model_buf; + struct nc_background *up_model_buf = NULL; + struct nc_background *next_model_p; + struct nc_background model; + struct encoder_setupt setup_mean, setup_var, setup_pix; + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; -static int encode_S_FX(struct cmp_cfg *cfg, struct encoder_struct *enc) -{ - struct S_FX *data_to_encode = cfg->input_buf; - size_t i; - struct cmp_cfg cfg_exp_flag = *cfg; - struct encoder_struct enc_exp_flag = *enc; + stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); - cfg_exp_flag.golomb_par = GOLOMB_PAR_EXPOSURE_FLAGS; - enc_exp_flag.log2_golomb_par = ilog_2(GOLOMB_PAR_EXPOSURE_FLAGS); + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } - for (i = 0; i < cfg->samples; i++) { - int err; + err = configure_encoder_setup(&setup_mean, cfg->cmp_par_mean, cfg->spill_mean, + cfg->round, max_used_bits.nc_background_mean, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_var, cfg->cmp_par_variance, cfg->spill_variance, + cfg->round, max_used_bits.nc_background_variance, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_pix, cfg->cmp_par_pixels_error, cfg->spill_pixels_error, + cfg->round, max_used_bits.nc_background_outlier_pixels, cfg); + if (err) + return -1; - /* err = encode_value(data_to_encode[i].EXPOSURE_FLAGS, 8, &cfg_exp_flag, enc); */ - err = encode_normal(data_to_encode[i].EXPOSURE_FLAGS, &cfg_exp_flag, &enc_exp_flag); - if (err) - return err; - enc->cmp_size = enc_exp_flag.cmp_size; + for (i = 0;; i++) { + stream_len = encode_value(data_buf[i].mean, model.mean, + stream_len, &setup_mean); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].variance, model.variance, + stream_len, &setup_var); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].outlier_pixels, model.outlier_pixels, + stream_len, &setup_pix); + if (stream_len <= 0) + return stream_len; + + if (up_model_buf) { + up_model_buf[i].mean = cmp_up_model(data_buf[i].mean, model.mean, + cfg->model_value, setup_mean.lossy_par); + up_model_buf[i].variance = cmp_up_model(data_buf[i].variance, model.variance, + cfg->model_value, setup_var.lossy_par); + up_model_buf[i].outlier_pixels = cmp_up_model(data_buf[i].outlier_pixels, model.outlier_pixels, + cfg->model_value, setup_pix.lossy_par); + } - enc->log2_golomb_par = ilog_2(cfg->golomb_par); - err = encode_value(data_to_encode[i].FX, 32, cfg, enc); - if (err) - return err; + if (i >= cfg->samples-1) + break; - enc_exp_flag.cmp_size = enc->cmp_size; + model = next_model_p[i]; } - - return 0; + return stream_len; } -static int encode_S_FX_EFX(struct cmp_cfg *cfg, struct encoder_struct *enc) +/** + * @brief compress smearing data from the normal cameras + * + * @param cfg pointer to the compression configuration structure + * + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream + */ + +static int compress_smearing(const struct cmp_cfg *cfg) { - struct S_FX_EFX *data_to_encode = cfg->input_buf; + int err; + int stream_len = 0; size_t i; - for (i = 0; i < cfg->samples; i++) { - int err; + struct smearing *data_buf = cfg->input_buf; + struct smearing *model_buf = cfg->model_buf; + struct smearing *up_model_buf = NULL; + struct smearing *next_model_p; + struct smearing model; + struct encoder_setupt setup_mean, setup_var_mean, setup_pix; - err = encode_value(data_to_encode[i].EXPOSURE_FLAGS, 8, cfg, enc); - if (err) - return err; + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; - err = encode_value(data_to_encode[i].FX, 32, cfg, enc); - if (err) - return err; + stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); - err = encode_value(data_to_encode[i].EFX, 32, cfg, enc); - if (err) - return err; + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; } - return 0; -} - -static int encode_S_FX_NCOB(struct cmp_cfg *cfg, struct encoder_struct *enc) -{ - struct S_FX_NCOB *data_to_encode = cfg->input_buf; - size_t i; - - for (i = 0; i < cfg->samples; i++) { - int err; - - err = encode_value(data_to_encode[i].EXPOSURE_FLAGS, 8, cfg, enc); - if (err) - return err; + err = configure_encoder_setup(&setup_mean, cfg->cmp_par_mean, cfg->spill_mean, + cfg->round, max_used_bits.smeating_mean, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_var_mean, cfg->cmp_par_variance, cfg->spill_variance, + cfg->round, max_used_bits.smeating_variance_mean, cfg); + if (err) + return -1; + err = configure_encoder_setup(&setup_pix, cfg->cmp_par_pixels_error, cfg->spill_pixels_error, + cfg->round, max_used_bits.smearing_outlier_pixels, cfg); + if (err) + return -1; - err = encode_value(data_to_encode[i].FX, 32, cfg, enc); - if (err) - return err; + for (i = 0;; i++) { + stream_len = encode_value(data_buf[i].mean, model.mean, + stream_len, &setup_mean); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].variance_mean, model.variance_mean, + stream_len, &setup_var_mean); + if (stream_len <= 0) + return stream_len; + stream_len = encode_value(data_buf[i].outlier_pixels, model.outlier_pixels, + stream_len, &setup_pix); + if (stream_len <= 0) + return stream_len; + + if (up_model_buf) { + up_model_buf[i].mean = cmp_up_model(data_buf[i].mean, model.mean, + cfg->model_value, setup_mean.lossy_par); + up_model_buf[i].variance_mean = cmp_up_model(data_buf[i].variance_mean, model.variance_mean, + cfg->model_value, setup_var_mean.lossy_par); + up_model_buf[i].outlier_pixels = cmp_up_model(data_buf[i].outlier_pixels, model.outlier_pixels, + cfg->model_value, setup_pix.lossy_par); + } - err = encode_value(data_to_encode[i].NCOB_X, 32, cfg, enc); - if (err) - return err; + if (i >= cfg->samples-1) + break; - err = encode_value(data_to_encode[i].NCOB_Y, 32, cfg, enc); - if (err) - return err; + model = next_model_p[i]; } - return 0; + return stream_len; } -static int encode_S_FX_EFX_NCOB_ECOB(struct cmp_cfg *cfg, struct encoder_struct - *enc) -{ - struct S_FX_EFX_NCOB_ECOB *data_to_encode = cfg->input_buf; - size_t i; - - for (i = 0; i < cfg->samples; i++) { - int err; - - err = encode_value(data_to_encode[i].EXPOSURE_FLAGS, 8, cfg, enc); - if (err) - return err; - - err = encode_value(data_to_encode[i].FX, 32, cfg, enc); - if (err) - return err; +/** + * @brief fill the last part of the bitstream with zeros + * + * @param cfg pointer to the compression configuration structure + * @param cmp_size length of the bitstream in bits + * + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream + */ - err = encode_value(data_to_encode[i].NCOB_X, 32, cfg, enc); - if (err) - return err; +static int pad_bitstream(const struct cmp_cfg *cfg, int cmp_size) +{ + unsigned int output_buf_len_bits, n_pad_bits; - err = encode_value(data_to_encode[i].NCOB_Y, 32, cfg, enc); - if (err) - return err; + if (cmp_size < 0) + return cmp_size; - err = encode_value(data_to_encode[i].EFX, 32, cfg, enc); - if (err) - return err; + /* maximum length of the bitstream/icu_output_buf in bits */ + output_buf_len_bits = cmp_buffer_length_to_bits(cfg->buffer_length, cfg->data_type); - err = encode_value(data_to_encode[i].ECOB_X, 32, cfg, enc); - if (err) - return err; + /* no padding in RAW mode*/ + if (cfg->cmp_mode == CMP_MODE_RAW) + return cmp_size; - err = encode_value(data_to_encode[i].ECOB_Y, 32, cfg, enc); - if (err) - return err; + n_pad_bits = 32 - ((unsigned int)cmp_size & 0x1FU); + if (n_pad_bits < 32) { + int n_bits = put_n_bits32(0, n_pad_bits, cmp_size, cfg->icu_output_buf, + output_buf_len_bits); + if (n_bits < 0) + return n_bits; } - return 0; -} - -/* pad the bitstream with zeros */ -int pad_bitstream(struct cmp_cfg *cfg, uint32_t cmp_size) -{ - int n_bits = 0; - - if (!cfg) - return -1; - - /* is padding needed */ - if (cmp_size) { - int n_pad_bits = 32U - (cmp_size & 0x1f); - if (n_pad_bits < 32) { - n_bits = put_n_bits32(0, cmp_size, n_pad_bits, - cfg->icu_output_buf, - cfg->buffer_length); - if (n_bits <= 0) - return -2; - } - } - return n_bits; + return cmp_size; } -uint32_t cmp_encode_data(struct cmp_cfg *cfg) -{ - struct encoder_struct enc; - int err, n_bits; +/** + * @brief change the endianness of the compressed data to big-endian + * + * @param cfg pointer to the compression configuration structure + * @param cmp_size length of the bitstream in bits + * + * @returns 0 on success; non-zero on failure + */ - enc.encoder = select_encoder(cfg->golomb_par); - enc.log2_golomb_par = ilog_2(cfg->golomb_par); - enc.cmp_size = 0; +static int cmp_data_to_big_endian(const struct cmp_cfg *cfg, unsigned int cmp_size) +{ +#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + size_t i; + uint32_t *p; - switch (cfg->cmp_mode) { - case MODE_RAW: - err = encode_raw_16(cfg, &enc); - break; - case MODE_MODEL_ZERO: - case MODE_MODEL_MULTI: - case MODE_DIFF_ZERO: - case MODE_DIFF_MULTI: - err = encode_16(cfg, &enc); - break; - case MODE_RAW_S_FX: - err = encode_raw_S_FX(cfg, &enc); - break; - case MODE_MODEL_ZERO_S_FX: - case MODE_MODEL_MULTI_S_FX: - case MODE_DIFF_ZERO_S_FX: - case MODE_DIFF_MULTI_S_FX: - err = encode_S_FX(cfg, &enc); - break; - case MODE_MODEL_ZERO_S_FX_EFX: - case MODE_MODEL_MULTI_S_FX_EFX: - case MODE_DIFF_ZERO_S_FX_EFX: - case MODE_DIFF_MULTI_S_FX_EFX: - err = encode_S_FX_EFX(cfg, &enc); - break; - case MODE_MODEL_ZERO_S_FX_NCOB: - case MODE_MODEL_MULTI_S_FX_NCOB: - case MODE_DIFF_ZERO_S_FX_NCOB: - case MODE_DIFF_MULTI_S_FX_NCOB: - err = encode_S_FX_NCOB(cfg, &enc); - break; - case MODE_MODEL_ZERO_S_FX_EFX_NCOB_ECOB: - case MODE_MODEL_MULTI_S_FX_EFX_NCOB_ECOB: - case MODE_DIFF_ZERO_S_FX_EFX_NCOB_ECOB: - case MODE_DIFF_MULTI_S_FX_EFX_NCOB_ECOB: - err = encode_S_FX_EFX_NCOB_ECOB(cfg, &enc); - break; - case MODE_MODEL_ZERO_32: - case MODE_MODEL_MULTI_32: - case MODE_DIFF_ZERO_32: - case MODE_DIFF_MULTI_32: - case MODE_MODEL_ZERO_F_FX: - case MODE_MODEL_MULTI_F_FX: - case MODE_DIFF_ZERO_F_FX: - case MODE_DIFF_MULTI_F_FX: - err = encode_32(cfg, &enc); - break; - default: - debug_print("Error: Compression mode not supported.\n"); - return -1; - break; + if (cfg->cmp_mode == CMP_MODE_RAW) { + int err = cmp_input_big_to_cpu_endianness(cfg->icu_output_buf, + cmp_size/CHAR_BIT, cfg->data_type); + return err; } - n_bits = pad_bitstream(cfg, enc.cmp_size); - if (n_bits < 0) - return n_bits; - -#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) - { - size_t i; - uint32_t *p = (uint32_t *)cfg->icu_output_buf; - - if (p) { - for (i = 0; i < cmp_bit_to_4byte(enc.cmp_size)/sizeof(uint32_t); i++) - cpu_to_be32s(&p[i]); - } + if (rdcu_supported_data_type_is_used(cfg->data_type)) { + p = cfg->icu_output_buf; + } else { + p = &cfg->icu_output_buf[MULTI_ENTRY_HDR_SIZE/sizeof(uint32_t)]; + cmp_size -= MULTI_ENTRY_HDR_SIZE * CHAR_BIT; } -#endif /*__BYTE_ORDER__ */ - if (err) - return err; - else - return enc.cmp_size; + for (i = 0; i < cmp_bit_to_4byte(cmp_size)/sizeof(uint32_t); i++) + cpu_to_be32s(&p[i]); +#else + /* do nothing data are already in big-endian */ + (void)cfg; +#endif /*__BYTE_ORDER__ */ + return 0; } /** * @brief compress data on the ICU * - * @param cfg compressor configuration contains all parameters required for - * compression - * @param info compressor information contains information of the executed - * compression + * @param cfg pointer to a compression configuration (created with the + * cmp_cfg_icu_create() function, setup with the cmp_cfg_xxx() functions) * - * @note this function violates the input_buf in place - * @note if icu_new_model_buf = model_buf or NULL, the model will be updated in place * @note the validity of the cfg structure is checked before the compression is * started - * @note when using the 1d-differencing mode or the raw mode (cmp_mode = 0,2,4), - * the model parameters (model_value, model_buf, rdcu_model_adr) are ignored - * @note the rdcu_***_adr configuration parameters are ignored for icu - * compression - * @note semi adaptive compression not supported; configuration parameters - * ap1\_golomb\_par, ap2\_golomb\_par, ap1\_spill ap2\_spill will be - * ignored; information parameters ap1_cmp_size, ap2_cmp_size will always - * be 0 - * - * @returns 0 on success, error otherwise + * + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF (-2) if the compressed data buffer is too small to + * hold the whole compressed data, CMP_ERROR_HIGH_VALUE (-3) if a data or + * model value is bigger than the max_used_bits parameter allows (set with + * the cmp_set_max_used_bits() function) */ -int icu_compress_data(struct cmp_cfg *cfg, struct cmp_info *info) +int icu_compress_data(const struct cmp_cfg *cfg) { - int err; int cmp_size = 0; - err = set_info(cfg, info); - if (err) - return err; + if (!cfg) + return -1; - err = icu_cmp_cfg_valid(cfg, info); - if (err) - return err; + if (cfg->samples == 0) /* nothing to compress we are done*/ + return 0; - err = cmp_pre_process(cfg); - if (err) - return err; + if (!cmp_cfg_is_valid(cfg)) + return -1; - err = cmp_map_to_pos(cfg); - if (err) - return err; + if (model_mode_is_used(cfg->cmp_mode) && !cfg->model_buf) + return -1; - cmp_size = cmp_encode_data(cfg); - if (cmp_size == -2 && info) - /* the icu_output_buf is to small to store the whole bitstream */ - info->cmp_err |= 1UL << SMALL_BUFFER_ERR_BIT; /* set small buffer error */ - if (cmp_size < 0) - return cmp_size; - if (info) - info->cmp_size = cmp_size; + if (raw_mode_is_used(cfg->cmp_mode)) { + if (cfg->samples > cfg->buffer_length) { + cmp_size = CMP_ERROR_SAMLL_BUF; + } else { + cmp_size = cmp_cal_size_of_data(cfg->samples, cfg->data_type); + if (cfg->icu_output_buf) + memcpy(cfg->icu_output_buf, cfg->input_buf, cmp_size); + cmp_size *= CHAR_BIT; /* convert to bits */ + } + } else { + switch (cfg->data_type) { + case DATA_TYPE_IMAGETTE: + case DATA_TYPE_IMAGETTE_ADAPTIVE: + case DATA_TYPE_SAT_IMAGETTE: + case DATA_TYPE_SAT_IMAGETTE_ADAPTIVE: + case DATA_TYPE_F_CAM_IMAGETTE: + case DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE: + cmp_size = compress_imagette(cfg); + break; + + case DATA_TYPE_S_FX: + cmp_size = compress_s_fx(cfg); + break; + case DATA_TYPE_S_FX_DFX: + cmp_size = compress_s_fx_efx(cfg); + break; + case DATA_TYPE_S_FX_NCOB: + cmp_size = compress_s_fx_ncob(cfg); + break; + case DATA_TYPE_S_FX_DFX_NCOB_ECOB: + cmp_size = compress_s_fx_efx_ncob_ecob(cfg); + break; + + case DATA_TYPE_F_FX: + cmp_size = compress_f_fx(cfg); + break; + case DATA_TYPE_F_FX_DFX: + cmp_size = compress_f_fx_efx(cfg); + break; + case DATA_TYPE_F_FX_NCOB: + cmp_size = compress_f_fx_ncob(cfg); + break; + case DATA_TYPE_F_FX_DFX_NCOB_ECOB: + cmp_size = compress_f_fx_efx_ncob_ecob(cfg); + break; + + case DATA_TYPE_L_FX: + cmp_size = compress_l_fx(cfg); + break; + case DATA_TYPE_L_FX_DFX: + cmp_size = compress_l_fx_efx(cfg); + break; + case DATA_TYPE_L_FX_NCOB: + cmp_size = compress_l_fx_ncob(cfg); + break; + case DATA_TYPE_L_FX_DFX_NCOB_ECOB: + cmp_size = compress_l_fx_efx_ncob_ecob(cfg); + break; + + case DATA_TYPE_OFFSET: + cmp_size = compress_nc_offset(cfg); + break; + case DATA_TYPE_BACKGROUND: + cmp_size = compress_nc_background(cfg); + break; + case DATA_TYPE_SMEARING: + cmp_size = compress_smearing(cfg); + break; + + case DATA_TYPE_F_CAM_OFFSET: + case DATA_TYPE_F_CAM_BACKGROUND: + case DATA_TYPE_UNKOWN: + default: + debug_print("Error: Data type not supported.\n"); + cmp_size = -1; + } + } - return 0; + if (cfg->icu_output_buf && cmp_size > 0) { + cmp_size = pad_bitstream(cfg, cmp_size); + cmp_data_to_big_endian(cfg, (unsigned int)cmp_size); + } + + return cmp_size; } diff --git a/lib/cmp_icu_new.c b/lib/cmp_icu_new.c deleted file mode 100644 index d69ab0f..0000000 --- a/lib/cmp_icu_new.c +++ /dev/null @@ -1,422 +0,0 @@ -/** - * @file icu_cmp.c - * @author Dominik Loidolt (dominik.loidolt@univie.ac.at), - * @date 2020 - * - * @copyright GPLv2 - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope 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. - * - * @brief software compression library - * @see Data Compression User Manual PLATO-UVIE-PL-UM-0001 - */ - - -#include <stdint.h> - -#include "cmp_debug.h" - - -/* return code if the bitstream buffer is too small to store the whole bitstream */ -#define CMP_ERROR_SAMLL_BUF -2 - -/* pointer to a code word generation function */ -typedef uint32_t (*generate_cw_pt)(uint32_t value, uint32_t encoder_par1, - uint32_t encoder_par2, uint32_t *cw); - -/* typedef uint32_t (*next_model_f_pt)(uint32_t model_mode_val, uint32_t diff_model_var); */ - -struct encoder_setupt { - /* unsigned int lossy_par; */ - /* next_model_f_pt *next_model_f; */ - generate_cw_pt generate_cw; /* pointer to the code word generation function */ - /* uint32_t updated_model; */ - uint32_t encoder_par1; - uint32_t encoder_par2; - uint32_t outlier_par; - uint32_t model_value; - uint32_t max_value_bits; /* how many bits are needed to represent the highest possible value */ - uint32_t max_bit_len; /* maximum length of the bitstream/icu_output_buf in bits */ - uint32_t *bitstream_adr; -}; - - -/** - * @brief map a signed value into a positive value range - * - * @param value_to_map signed value to map - * @param max_value_bits how many bits are needed to represent the - * highest possible value - * - * @returns the positive mapped value - */ - -static uint32_t map_to_pos(uint32_t value_to_map, unsigned int max_value_bits) -{ - uint32_t mask = (~0U >> (32 - max_value_bits)); /* mask the used bits */ - - value_to_map &= mask; - if (value_to_map >> (max_value_bits - 1)) { /* check the leading signed bit */ - value_to_map |= ~mask; /* convert to 32-bit signed integer */ - /* map negative values to uneven numbers */ - return (-value_to_map) * 2 - 1; /* possible integer overflow is intended */ - } else { - /* map positive values to even numbers */ - return value_to_map * 2; /* possible integer overflow is intended */ - } -} - - -/** - * @brief put the value of up to 32 bits into a bitstream accessed as 32-bit - * RAM in big-endian - * - * @param value the value to put - * @param n_bits number of bits to put in the bitstream - * @param bit_offset bit index where the bits will be put, seen from the very - * beginning of the bitstream - * @param bitstream_adr this is the pointer to the beginning of the bitstream - * (can be NULL) - * @param max_bit_len maximum length of the bitstream in bits; is ignored if - * bitstream_adr is NULL - * - * @returns length in bits of the generated bitstream on success; returns - * negative in case of erroneous input; returns CMP_ERROR_SAMLL_BUF if - * the bitstream buffer is too small to put the value in the bitstream - */ - -static int put_n_bits32(uint32_t value, unsigned int n_bits, int bit_offset, - uint32_t *bitstream_adr, unsigned int max_bit_len) -{ - uint32_t *local_adr; - uint32_t mask; - unsigned int shiftRight, shiftLeft, bitsLeft, bitsRight; - int stream_len = (int)(n_bits + (unsigned int)bit_offset); /* overflow results in a negative return value */ - - /* leave in case of erroneous input */ - if (bit_offset < 0) - return -1; - - if (n_bits == 0) - return stream_len; - - if (n_bits > 32) - return -1; - - /* Do we need to write data to the bitstream? */ - if (!bitstream_adr) - return stream_len; - - /* Check if bitstream buffer is large enough */ - if ((unsigned int)stream_len > max_bit_len) { - debug_print("Error: The buffer for the compressed data is too small to hold the compressed data. Try a larger buffer_length parameter.\n"); - return CMP_ERROR_SAMLL_BUF; - } - - /* (M) is the n_bits parameter large enough to cover all value bits; the - * calculations can be re-used in the unsegmented code, so we have no overhead - */ - shiftRight = 32 - n_bits; - mask = 0xFFFFFFFFU >> shiftRight; - value &= mask; - - /* Separate the bit_offset into word offset (set local_adr pointer) and local bit offset (bitsLeft) */ - local_adr = bitstream_adr + (bit_offset >> 5); - bitsLeft = bit_offset & 0x1F; - - /* Calculate the bitsRight for the unsegmented case. If bitsRight is - * negative we need to split the value over two words - */ - bitsRight = shiftRight - bitsLeft; - - if ((int)bitsRight >= 0) { - /* UNSEGMENTED - * - *|-----------|XXXXX|----------------| - * bitsLeft n bitsRight - * - * -> to get the mask: - * shiftRight = bitsLeft + bitsRight = 32 - n - * shiftLeft = bitsRight = 32 - n - bitsLeft = shiftRight - bitsLeft - */ - - shiftLeft = bitsRight; - - /* generate the mask, the bits for the values will be true - * shiftRight = 32 - n_bits; see (M) above! - * mask = (0XFFFFFFFF >> shiftRight) << shiftLeft; see (M) above! - */ - mask <<= shiftLeft; - value <<= shiftLeft; - - /* clear the destination with inverse mask */ - *(local_adr) &= ~mask; - - /* assign the value */ - *(local_adr) |= value; - - } else { - /* SEGMENTED - * - *|-----------------------------|XXX| |XX|------------------------------| - * bitsLeft n1 n2 bitsRight - * - * -> to get the mask part 1: - * shiftRight = bitsLeft - * n1 = n - (bitsLeft + n - 32) = 32 - bitsLeft - * - * -> to get the mask part 2: - * n2 = bitsLeft + n - 32 = -(32 - n - bitsLeft) = -(bitsRight_UNSEGMENTED) - * shiftLeft = 32 - n2 = 32 - (bitsLeft + n - 32) = 64 - bitsLeft - n - * - */ - - unsigned int n2 = -bitsRight; - - /* part 1: */ - shiftRight = bitsLeft; - mask = 0XFFFFFFFFU >> shiftRight; - - /* clear the destination with inverse mask */ - *(local_adr) &= ~mask; - - /* assign the value part 1 */ - *(local_adr) |= (value >> n2); - - /* part 2: */ - /* adjust address */ - local_adr += 1; - shiftLeft = 32 - n2; - mask = 0XFFFFFFFFU << shiftLeft; - - /* clear the destination with inverse mask */ - *(local_adr) &= ~mask; - - /* assign the value part 2 */ - *(local_adr) |= (value << shiftLeft); - } - return stream_len; -} - - -/** - * @brief forms the codeword according to the Rice code - * - * @param value value to be encoded - * @param m Golomb parameter, only m's which are power of 2 are allowed - * @param log2_m Rice parameter, is log_2(m) calculate outside function - * for better performance - * @param cw address were the encode code word is stored - * - * @returns the length of the formed code word in bits - * @note no check if the generated code word is not longer than 32 bits. - */ - -static uint32_t Rice_encoder(uint32_t value, uint32_t m, uint32_t log2_m, - uint32_t *cw) -{ - uint32_t g; /* quotient of value/m */ - uint32_t q; /* quotient code without ending zero */ - uint32_t r; /* remainder of value/m */ - uint32_t rl; /* remainder length */ - - g = value >> log2_m; /* quotient, number of leading bits */ - q = (1U << g) - 1; /* prepare the quotient code without ending zero */ - - r = value & (m-1); /* calculate the remainder */ - rl = log2_m + 1; /* length of the remainder (+1 for the 0 in the quotient code) */ - *cw = (q << rl) | r; /* put the quotient and remainder code together */ - /* - * NOTE: If log2_m = 31 -> rl = 32, (q << rl) leads to an undefined - * behavior. However, in this case, a valid code with a maximum of 32 - * bits can only be formed if q = 0. Any shift with 0 << x always - * results in 0, which forms the correct codeword in this case. For - * performance reasons, this undefined behaviour is not caught. - */ - - return rl + g; /* calculate the length of the code word */ -} - - -/** - * @brief forms a codeword according to the Golomb code - * - * @param value value to be encoded - * @param m Golomb parameter (have to be bigger than 0) - * @param log2_m is log_2(m) calculate outside function for better - * performance - * @param cw address were the formed code word is stored - * - * @returns the length of the formed code word in bits - */ - -static uint32_t Golomb_encoder(uint32_t value, uint32_t m, uint32_t log2_m, - uint32_t *cw) -{ - uint32_t len0, b, g, q, lg; - uint32_t len; - uint32_t cutoff; - - len0 = log2_m + 1; /* codeword length in group 0 */ - cutoff = (1U << (log2_m + 1)) - m; /* members in group 0 */ - - if (value < cutoff) { /* group 0 */ - *cw = value; - len = len0; - } else { /* other groups */ - g = (value-cutoff) / m; /* this group is which one */ - b = cutoff << 1; /* form the base codeword */ - lg = len0 + g; /* it has lg remainder bits */ - q = (1U << g) - 1; /* prepare the left side in unary */ - *cw = (q << (len0+1)) + b + (value-cutoff) - g*m; /* composed codeword */ - len = lg + 1; /* length of the codeword */ - } - return len; -} - - -/** - * @brief generate a code word without a outlier mechanism and put in the - * bitstream - * - * @param value value to encode in the bitstream - * @param stream_len length of the bitstream in bits - * @param setup pointer to the encoder setup - * - * @returns the bit length of the bitstream with the added encoded value on - * success; negative on error - */ - -static int encode_normal(uint32_t value, int stream_len, - struct encoder_setupt *setup) -{ - uint32_t code_word, cw_len; - - cw_len = setup->generate_cw(value, setup->encoder_par1, - setup->encoder_par2, &code_word); - - return put_n_bits32(code_word, cw_len, stream_len, setup->bitstream_adr, - setup->max_bit_len); -} - - -/** - * @brief subtract the model from the data, encode the result and put it into - * bitstream, for encodeing outlier use the zero escape symbol mechanism - * - * @param data data to encode - * @param model model of the data (0 if not used) - * @param stream_len length of the bitstream in bits - * @param setup pointer to the encoder setup - * - * @returns the bit length of the bitstream with the added encoded value on - * success; negative on error, CMP_ERROR_SAMLL_BUF if the bitstream buffer - * is too small to put the value in the bitstream - * - * @note no check if data or model are in the allowed range - * @note no check if the setup->outlier_par is in the allowed range - */ - -static int encode_value_zero(uint32_t data, uint32_t model, int stream_len, - struct encoder_setupt *setup) -{ - data -= model; - - data = map_to_pos(data, setup->max_value_bits); - - /* For performance reasons, we check to see if there is an outlier - * before adding one, rather than the other way around: - * data++; - * if (data < setup->outlier_par && data != 0) - * return ... - */ - if (data < (setup->outlier_par - 1)) { /* detect r */ - data++; /* add 1 to every value so we can use 0 as escape symbol */ - return encode_normal(data, stream_len, setup); - } - - data++; /* add 1 to every value so we can use 0 as escape symbol */ - - /* use zero as escape symbol */ - stream_len = encode_normal(0, stream_len, setup); - if (stream_len <= 0) - return stream_len; - - /* put the data unencoded in the bitstream */ - stream_len = put_n_bits32(data, setup->max_value_bits, stream_len, - setup->bitstream_adr, setup->max_bit_len); - - return stream_len; - -} - - -/** - * @brief subtract the model from the data, encode the result and put it into - * bitstream, for encoding outlier use the multi escape symbol mechanism - * - * @param data data to encode - * @param model model of the data (0 if not used) - * @param stream_len length of the bitstream in bits - * @param setup pointer to the encoder setup - * - * @returns the bit length of the bitstream with the added encoded value on - * success; negative on error, CMP_ERROR_SAMLL_BUF if the bitstream buffer - * is too small to put the value in the bitstream - * - * @note no check if data or model are in the allowed range - * @note no check if the setup->outlier_par is in the allowed ragne - */ - -static int encode_value_multi(uint32_t data, uint32_t model, int stream_len, - struct encoder_setupt *setup) -{ - uint32_t unencoded_data; - unsigned int unencoded_data_len; - uint32_t escape_sym, escape_sym_offset; - - data -= model; /* possible underflow is intended */ - - data = map_to_pos(data, setup->max_value_bits); - - if (data < setup->outlier_par) /* detect non-outlier */ - return encode_normal(data, stream_len, setup); - - /* - * In this mode we put the difference between the data and the spillover - * threshold value (unencoded_data) after a encoded escape symbol, which - * indicate that the next codeword is unencoded. - * We use different escape symbol depended on the size the needed bit of - * unencoded data: - * 0, 1, 2 bits needed for unencoded data -> escape symbol is outlier_par + 0 - * 3, 4 bits needed for unencoded data -> escape symbol is outlier_par + 1 - * 5, 6 bits needed for unencoded data -> escape symbol is outlier_par + 2 - * and so on - */ - unencoded_data = data - setup->outlier_par; - - if (!unencoded_data) /* catch __builtin_clz(0) because the result is undefined.*/ - escape_sym_offset = 0; - else - escape_sym_offset = (31U - (uint32_t)__builtin_clz(unencoded_data)) >> 1; - - escape_sym = setup->outlier_par + escape_sym_offset; - unencoded_data_len = (escape_sym_offset + 1U) << 1; - - /* put the escape symbol in the bitstream */ - stream_len = encode_normal(escape_sym, stream_len, setup); - if (stream_len <= 0) - return stream_len; - - /* put the unencoded data in the bitstream */ - stream_len = put_n_bits32(unencoded_data, unencoded_data_len, stream_len, - setup->bitstream_adr, setup->max_bit_len); - - return stream_len; -} diff --git a/lib/cmp_io.c b/lib/cmp_io.c index 7c00d56..99e80c6 100644 --- a/lib/cmp_io.c +++ b/lib/cmp_io.c @@ -32,6 +32,38 @@ #include "cmp_data_types.h" +/* directory to convert from data_type to string */ +static const struct { + enum cmp_data_type data_type; + const char *str; +} data_type_string_table[] = { + {DATA_TYPE_IMAGETTE, "DATA_TYPE_IMAGETTE"}, + {DATA_TYPE_IMAGETTE_ADAPTIVE, "DATA_TYPE_IMAGETTE_ADAPTIVE"}, + {DATA_TYPE_SAT_IMAGETTE, "DATA_TYPE_SAT_IMAGETTE"}, + {DATA_TYPE_SAT_IMAGETTE_ADAPTIVE, "DATA_TYPE_SAT_IMAGETTE_ADAPTIVE"}, + {DATA_TYPE_OFFSET, "DATA_TYPE_OFFSET"}, + {DATA_TYPE_BACKGROUND, "DATA_TYPE_BACKGROUND"}, + {DATA_TYPE_SMEARING, "DATA_TYPE_SMEARING"}, + {DATA_TYPE_S_FX, "DATA_TYPE_S_FX"}, + {DATA_TYPE_S_FX_DFX, "DATA_TYPE_S_FX_DFX"}, + {DATA_TYPE_S_FX_NCOB, "DATA_TYPE_S_FX_NCOB"}, + {DATA_TYPE_S_FX_DFX_NCOB_ECOB, "DATA_TYPE_S_FX_DFX_NCOB_ECOB"}, + {DATA_TYPE_L_FX, "DATA_TYPE_L_FX"}, + {DATA_TYPE_L_FX_DFX, "DATA_TYPE_L_FX_DFX"}, + {DATA_TYPE_L_FX_NCOB, "DATA_TYPE_L_FX_NCOB"}, + {DATA_TYPE_L_FX_DFX_NCOB_ECOB, "DATA_TYPE_L_FX_DFX_NCOB_ECOB"}, + {DATA_TYPE_F_FX, "DATA_TYPE_F_FX"}, + {DATA_TYPE_F_FX_DFX, "DATA_TYPE_F_FX_DFX"}, + {DATA_TYPE_F_FX_NCOB, "DATA_TYPE_F_FX_NCOB"}, + {DATA_TYPE_F_FX_DFX_NCOB_ECOB, "DATA_TYPE_F_FX_DFX_NCOB_ECOB"}, + {DATA_TYPE_F_CAM_IMAGETTE, "DATA_TYPE_F_CAM_IMAGETTE"}, + {DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE, "DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE"}, + {DATA_TYPE_F_CAM_OFFSET, "DATA_TYPE_F_CAM_OFFSET"}, + {DATA_TYPE_F_CAM_BACKGROUND, "DATA_TYPE_F_CAM_BACKGROUND"}, + {DATA_TYPE_UNKOWN, "DATA_TYPE_UNKOWN"} +}; + + /** * @brief print help information * @@ -114,31 +146,34 @@ static FILE *open_file(const char *dirname, const char *filename) /** - * @brief write a uint16_t buffer to an output file - * - * @param buf the buffer to write a file - * @param buf_len length of the buffer + * @brief write uncompressed input data to an output file * + * @param data the data to write a file + * @param data_size size of the data in bytes * @param output_prefix file name without file extension * @param name_extension file extension (with leading point character) - * * @param verbose print verbose output if not zero * * @returns 0 on success, error otherwise */ -int write_to_file16(const uint16_t *buf, uint32_t buf_len, const char - *output_prefix, const char *name_extension, int verbose) +int write_input_data_to_file(void *data, uint32_t data_size, enum cmp_data_type data_type, + const char *output_prefix, const char *name_extension, int verbose) { - uint32_t i; + uint32_t i = 0; FILE *fp; + uint8_t *tmp_buf; + size_t sample_size = size_of_a_sample(data_type); - if (!buf) + if (!data) abort(); - if (buf_len == 0) + if (data_size == 0) return 0; + if (!sample_size) + return -1; + fp = open_file(output_prefix, name_extension); if (fp == NULL) { fprintf(stderr, "%s: %s%s: %s\n", PROGRAM_NAME, output_prefix, @@ -146,8 +181,12 @@ int write_to_file16(const uint16_t *buf, uint32_t buf_len, const char return -1; } - for (i = 0; i < buf_len; i++) { - fprintf(fp, "%02X %02X", buf[i] >> 8, buf[i] & 0xFF); + tmp_buf = malloc(data_size); + memcpy(tmp_buf, data, data_size); + cmp_input_big_to_cpu_endianness(tmp_buf, data_size, data_type); + + for (i = 0 ; i < data_size; i++) { + fprintf(fp, "%02X", tmp_buf[i]); if ((i + 1) % 16 == 0) fprintf(fp, "\n"); else @@ -159,8 +198,8 @@ int write_to_file16(const uint16_t *buf, uint32_t buf_len, const char if (verbose) { printf("\n\n"); - for (i = 0; i < buf_len; i++) { - printf("%02X %02X", buf[i] >> 8, buf[i] & 0xFF); + for (i = 0; i < data_size; i++) { + printf("%02X ", tmp_buf[i]); if ((i + 1) % 16 == 0) printf("\n"); else @@ -170,6 +209,7 @@ int write_to_file16(const uint16_t *buf, uint32_t buf_len, const char printf("\n\n"); } + free(tmp_buf); return 0; } @@ -356,6 +396,67 @@ int atoui32(const char *dep_str, const char *val_str, uint32_t *red_val) } +/** + * @brief parse a compression data_type string to a data_type + * @note string can be either a number or the name of the compression data type + * + * @param data_type_str string containing the compression data type to parse + * + * @returns data type on success, DATA_TYPE_UNKOWN on error + */ + +enum cmp_data_type string2data_type(const char *data_type_str) +{ + enum cmp_data_type data_type = DATA_TYPE_UNKOWN; + + if (data_type_str) { + if (isalpha(data_type_str[0])) { /* check if mode is given as text */ + size_t j; + + for (j = 0; j < sizeof(data_type_string_table) / sizeof(data_type_string_table[0]); j++) { + if (!strcmp(data_type_str, data_type_string_table[j].str)) { + data_type = data_type_string_table[j].data_type; + break; + } + } + } else { + uint32_t read_val; + + if (!atoui32("Compression Data Type", data_type_str, &read_val)) { + data_type = read_val; + if (!cmp_data_type_valid(data_type)) + data_type = DATA_TYPE_UNKOWN; + } + } + } + return data_type; +} + +/** + * @brief parse a compression data_type string to a data_type + * @note string can be either a number or the name of the compression data type + * + * @param data_type compression data type to convert in string + * + * @returns data type on success, DATA_TYPE_UNKOWN on error + */ + +const char *data_type2string(enum cmp_data_type data_type) +{ + size_t j; + const char *string = "DATA_TYPE_UNKOWN"; + + for (j = 0; j < sizeof(data_type_string_table) / sizeof(data_type_string_table[0]); j++) { + if (data_type == data_type_string_table[j].data_type) { + string = data_type_string_table[j].str; + break; + } + } + + return string; +} + + /** * @brief parse a compression mode vale string to an integer * @note string can be either a number or the name of the compression mode @@ -373,11 +474,16 @@ int cmp_mode_parse(const char *cmp_mode_str, uint32_t *cmp_mode) uint32_t cmp_mode; const char *str; } conversion[] = { - {MODE_RAW, "MODE_RAW"}, - {MODE_MODEL_ZERO, "MODE_MODEL_ZERO"}, - {MODE_DIFF_ZERO, "MODE_DIFF_ZERO"}, - {MODE_MODEL_MULTI, "MODE_MODEL_MULTI"}, - {MODE_DIFF_MULTI, "MODE_DIFF_MULTI"}, + {CMP_MODE_RAW, "MODE_RAW"}, + {CMP_MODE_MODEL_ZERO, "MODE_MODEL_ZERO"}, + {CMP_MODE_DIFF_ZERO, "MODE_DIFF_ZERO"}, + {CMP_MODE_MODEL_MULTI, "MODE_MODEL_MULTI"}, + {CMP_MODE_DIFF_MULTI, "MODE_DIFF_MULTI"}, + {CMP_MODE_RAW, "CMP_MODE_RAW"}, + {CMP_MODE_MODEL_ZERO, "CMP_MODE_MODEL_ZERO"}, + {CMP_MODE_DIFF_ZERO, "CMP_MODE_DIFF_ZERO"}, + {CMP_MODE_MODEL_MULTI, "CMP_MODE_MODEL_MULTI"}, + {CMP_MODE_DIFF_MULTI, "CMP_MODE_DIFF_MULTI"}, }; if (!cmp_mode_str) @@ -398,7 +504,7 @@ int cmp_mode_parse(const char *cmp_mode_str, uint32_t *cmp_mode) return -1; } - if (!cmp_mode_available(*cmp_mode)) + if (!cmp_mode_is_supported(*cmp_mode)) return -1; return 0; @@ -450,9 +556,16 @@ static int parse_cfg(FILE *fp, struct cmp_cfg *cfg) continue; + if (!strcmp(token1, "data_type")) { + cfg->data_type = string2data_type(token2); + if (cfg->data_type == DATA_TYPE_UNKOWN) + return -1; + continue; + } if (!strcmp(token1, "cmp_mode")) { must_read_items[CMP_MODE] = 1; if (isalpha(*token2)) { /* check if mode is given as text or val*/ + /* TODO: use conversion function for this: */ if (!strcmp(token2, "MODE_RAW")) { cfg->cmp_mode = 0; continue; @@ -645,12 +758,6 @@ int read_cmp_cfg(const char *file_name, struct cmp_cfg *cfg, int verbose_en) printf("\n"); } - if (cfg->buffer_length < cfg->samples/2) { - fprintf(stderr, - "%s: warning: buffer_length: %u is relative small to the chosen samples parameter of %u.\n", - PROGRAM_NAME, cfg->buffer_length, cfg->samples); - } - return 0; } @@ -705,6 +812,7 @@ static int parse_info(FILE *fp, struct cmp_info *info) if (!strcmp(token1, "cmp_mode_used")) { must_read_items[CMP_MODE_USED] = 1; if (isalpha(*token2)) { /* check if mode is given as text or val*/ + /* TODO: use conversion function for this: */ if (!strcmp(token2, "MODE_RAW")) { info->cmp_mode_used = 0; continue; @@ -1013,7 +1121,7 @@ static uint8_t str_to_uint8(const char *str, char **str_end) /** - * @brief reads n_word words of a hex-encoded string to a uint8_t buffer + * @brief reads buf_size words of a hex-encoded string to a uint8_t buffer * * @note A whitespace (space (0x20), form feed (0x0c), line feed (0x0a), carriage * return (0x0d), horizontal tab (0x09), or vertical tab (0x0b) or several in a @@ -1026,14 +1134,14 @@ static uint8_t str_to_uint8(const char *str, char **str_end) * * @param str pointer to the null-terminated byte string to be interpreted * @param data buffer to write the interpreted content (can be NULL) - * @param n_word number of uint8_t data words to read in + * @param buf_size number of uint8_t data words to read in * @param file_name file name for better error output (can be NULL) * @param verbose_en print verbose output if not zero * * @returns the size in bytes to store the string content; negative on error */ -static ssize_t str2uint8_arr(const char *str, uint8_t *data, uint32_t n_word, +static ssize_t str2uint8_arr(const char *str, uint8_t *data, uint32_t buf_size, const char *file_name, int verbose_en) { const char *nptr = str; @@ -1043,12 +1151,12 @@ static ssize_t str2uint8_arr(const char *str, uint8_t *data, uint32_t n_word, errno = 0; if (!data) - n_word = ~0; + buf_size = ~0; if (!file_name) file_name = "unknown file name"; - for (i = 0; i < n_word; ) { + for (i = 0; i < buf_size; ) { uint8_t read_val; unsigned char c = *nptr; @@ -1118,8 +1226,7 @@ static ssize_t str2uint8_arr(const char *str, uint8_t *data, uint32_t n_word, /** - * @brief reads n_word words of a hex-encoded uint8_t data form a file to a - * buffer + * @brief reads hex-encoded uint8_t data form a file to a buffer * * @note A whitespace (space (0x20), form feed (0x0c), line feed (0x0a), carriage * return (0x0d), horizontal tab (0x09), or vertical tab (0x0b) or several in a @@ -1132,13 +1239,13 @@ static ssize_t str2uint8_arr(const char *str, uint8_t *data, uint32_t n_word, * * @param file_name data/model file name * @param buf buffer to write the file content (can be NULL) - * @param n_word number of uint8_t data words to read in + * @param buf_size number of uint8_t data words to read in * @param verbose_en print verbose output if not zero * * @returns the size in bytes to store the file content; negative on error */ -ssize_t read_file8(const char *file_name, uint8_t *buf, uint32_t n_word, int verbose_en) +ssize_t read_file8(const char *file_name, uint8_t *buf, uint32_t buf_size, int verbose_en) { FILE *fp; char *file_cpy = NULL; @@ -1165,6 +1272,10 @@ ssize_t read_file8(const char *file_name, uint8_t *buf, uint32_t n_word, int ver fclose(fp); return 0; } + if (file_size < buf_size) { + fprintf(stderr, "%s: %s: Error: The files do not contain enough data as requested.\n", PROGRAM_NAME, file_name); + goto fail; + } /* reset the file position indicator to the beginning of the file */ if (fseek(fp, 0L, SEEK_SET) != 0) goto fail; @@ -1188,7 +1299,7 @@ ssize_t read_file8(const char *file_name, uint8_t *buf, uint32_t n_word, int ver fclose(fp); fp = NULL; - size = str2uint8_arr(file_cpy, buf, n_word, file_name, verbose_en); + size = str2uint8_arr(file_cpy, buf, buf_size, file_name, verbose_en); free(file_cpy); file_cpy = NULL; @@ -1206,75 +1317,38 @@ fail: /** - * @brief reads the number of uint16_t samples of a hex-encoded data (or model) - * file into a buffer + * @brief reads hex-encoded data from a file into a buffer * * @param file_name data/model file name + * @param data_type compression data type used for the data * @param buf buffer to write the file content (can be NULL) - * @param samples amount of uint16_t data samples to read in + * @param buf_size size in bytes of the buffer * @param verbose_en print verbose output if not zero * * @returns the size in bytes to store the file content; negative on error */ -ssize_t read_file16(const char *file_name, uint16_t *buf, uint32_t samples, - int verbose_en) +ssize_t read_file_data(const char *file_name, enum cmp_data_type data_type, + void *buf, uint32_t buf_size, int verbose_en) { - ssize_t size = read_file8(file_name, (uint8_t *)buf, - samples*sizeof(uint16_t), verbose_en); + ssize_t size; + int samples, err; + + size = read_file8(file_name, (uint8_t *)buf, buf_size, verbose_en); if (size < 0) return size; - if (size & 0x1) { - fprintf(stderr, "%s: %s: Error: The data are not correct formatted. Expected multiple of 2 hex words.\n", + samples = cmp_input_size_to_samples(size, data_type); + if (samples < 0) { + fprintf(stderr, "%s: %s: Error: The data are not correct formatted for the used compression data type.\n", PROGRAM_NAME, file_name); return -1; } - if (buf) { - size_t i; - for (i = 0; i < samples; i++) - be16_to_cpus(&buf[i]); - } - - - return size; -} - - -/** - * @brief reads the number of uint32_t samples of a hex-encoded data (or model) - * file into a buffer - * - * @param file_name data/model file name - * @param buf buffer to write the file content (can be NULL) - * @param samples amount of uint32_t data samples to read in - * @param verbose_en print verbose output if not zero - * - * @returns the size in bytes to store the file content; negative on error - */ - -ssize_t read_file32(const char *file_name, uint32_t *buf, uint32_t samples, - int verbose_en) -{ - ssize_t size = read_file8(file_name, (uint8_t *)buf, - samples*sizeof(uint32_t), verbose_en); - - if (size < 0) - return -1; - - if (size & 0x3) { - fprintf(stderr, "%s: %s: Error: The data are not correct formatted. Expected multiple of 4 hex words.\n", - PROGRAM_NAME, file_name); + err = cmp_input_big_to_cpu_endianness(buf, size, data_type); + if (err) return -1; - } - - if (buf) { - size_t i; - for (i = 0; i < samples; i++) - be32_to_cpus(&buf[i]); - } return size; } @@ -1292,7 +1366,7 @@ ssize_t read_file32(const char *file_name, uint32_t *buf, uint32_t samples, */ ssize_t read_file_cmp_entity(const char *file_name, struct cmp_entity *ent, - uint32_t ent_size, int verbose_en) + uint32_t ent_size, int verbose_en) { ssize_t size; @@ -1307,9 +1381,9 @@ ssize_t read_file_cmp_entity(const char *file_name, struct cmp_entity *ent, } if (ent) { - enum cmp_ent_data_type data_type = cmp_ent_get_data_type(ent); + enum cmp_data_type data_type = cmp_ent_get_data_type(ent); - if (!cmp_ent_data_type_valid(data_type)) { + if (data_type == DATA_TYPE_UNKOWN) { fprintf(stderr, "%s: %s: Error: Compression data type is not supported.\n", PROGRAM_NAME, file_name); return -1; @@ -1328,6 +1402,41 @@ ssize_t read_file_cmp_entity(const char *file_name, struct cmp_entity *ent, } +/** + * @brief reads hex-encoded uint32_t samples from a file into a buffer + * + * @param file_name data/model file name + * @param buf buffer to write the file content (can be NULL) + * @param buf_size size of the buf buffer in bytes + * @param verbose_en print verbose output if not zero + * + * @returns the size in bytes to store the file content; negative on error + */ + +ssize_t read_file32(const char *file_name, uint32_t *buf, uint32_t buf_size, + int verbose_en) +{ + ssize_t size = read_file8(file_name, (uint8_t *)buf, buf_size, verbose_en); + + if (size < 0) + return -1; + + if (size & 0x3) { + fprintf(stderr, "%s: %s: Error: The data are not correct formatted. Expected multiple of 4 hex words.\n", + PROGRAM_NAME, file_name); + return -1; + } + + if (buf) { + size_t i; + for (i = 0; i < buf_size/sizeof(uint32_t); i++) + be32_to_cpus(&buf[i]); + } + + return size; +} + + /* * @brief generate from the cmp_tool version string a version_id for the * compression entity header @@ -1371,7 +1480,7 @@ uint32_t cmp_tool_gen_version_id(const char *version) version_id |= n; - return version_id |= CMP_TOOL_VERSION_ID_BIT; + return version_id | CMP_TOOL_VERSION_ID_BIT; } @@ -1389,6 +1498,10 @@ static void write_cfg_internal(FILE *fp, const struct cmp_cfg *cfg, int rdcu_cfg fprintf(fp, "#-------------------------------------------------------------------------------\n"); fprintf(fp, "# Default Configuration File\n"); fprintf(fp, "#-------------------------------------------------------------------------------\n"); + fprintf(fp, "# Selected compression data type\n"); + fprintf(fp, "\n"); + fprintf(fp, "data_type = %u\n", cfg->data_type); + fprintf(fp, "\n"); fprintf(fp, "# Selected compression mode\n"); fprintf(fp, "# 0: raw mode\n"); fprintf(fp, "# 1: model mode with zero escape symbol mechanism\n"); diff --git a/lib/cmp_rdcu.c b/lib/cmp_rdcu.c index c24ebed..34606f4 100644 --- a/lib/cmp_rdcu.c +++ b/lib/cmp_rdcu.c @@ -15,21 +15,36 @@ * * @brief hardware compressor control library * @see Data Compression User Manual PLATO-UVIE-PL-UM-0001 + * + * To compress data, first create a compression configuration with the + * rdcu_cfg_create() function. + * Then set the different data buffers with the data to compressed, the model + * data and the RDCU SRAM addresses with the rdcu_cfg_buffers() function. + * Then set the imagette compression parameters with the rdcu_cfg_imagette() + * function. + * Finally, you can compress the data with the RDCU using the + * rdcu_compress_data() function. */ #include <stdint.h> #include <stdio.h> -#include "../include/rdcu_cmd.h" -#include "../include/cmp_support.h" -#include "../include/rdcu_ctrl.h" -#include "../include/rdcu_rmap.h" +#include "rdcu_cmd.h" +#include "cmp_support.h" +#include "cmp_data_types.h" +#include "rdcu_ctrl.h" +#include "rdcu_rmap.h" +#include "cmp_debug.h" + +#define IMA_SAM2BYT \ + 2 /* imagette sample to byte conversion factor; one imagette samples has 16 bits (2 bytes) */ #define RDCU_INTR_SIG_ENA 1 /* RDCU interrupt signal enabled */ #define RDCU_INTR_SIG_DIS 0 /* RDCU interrupt signal disable */ -#define RDCU_INTR_SIG_DEFAULT RDCU_INTR_SIG_ENA /* default start value for RDCU - interrupt signal */ +#define RDCU_INTR_SIG_DEFAULT RDCU_INTR_SIG_ENA /* default start value for RDCU interrupt signal */ + + /* RDCU interrupt signal status */ static int interrupt_signal_enabled = RDCU_INTR_SIG_DEFAULT; @@ -48,6 +63,7 @@ static void sync(void) not needed for packed generation int cnt = 0; + printf("syncing..."); while (rdcu_rmap_sync_status()) { printf("pending: %d\n", rdcu_rmap_sync_status()); @@ -63,6 +79,84 @@ static void sync(void) } +/** + * @brief check if the compression data product type, compression mode, model + * value and the lossy rounding parameters are valid for a RDCU compression + * + * @param cfg pointer to a compression configuration containing the compression + * data product type, compression mode, model value and the rounding parameters + * + * @returns 0 if the compression data type, compression mode, model value and + * the lossy rounding parameters are valid for a RDCU compression, non-zero + * if parameters are invalid + */ + +static int rdcu_cfg_gen_par_is_invalid(const struct cmp_cfg *cfg) +{ + int cfg_invalid = 0; + + if (!cfg) + return -1; + + if (!cmp_imagette_data_type_is_used(cfg->data_type)) { + debug_print("Error: The selected compression data type is not supported for RDCU compression"); + cfg_invalid++; + } + + if (cfg->cmp_mode > MAX_RDCU_CMP_MODE) { + debug_print("Error: selected cmp_mode: %u is not supported. Largest supported mode is: %u.\n", + cfg->cmp_mode, MAX_RDCU_CMP_MODE); + cfg_invalid++; + } + + if (cfg->model_value > MAX_MODEL_VALUE) { + debug_print("Error: selected model_value: %u is invalid. Largest supported value is: %u.\n", + cfg->model_value, MAX_MODEL_VALUE); + cfg_invalid++; + } + + if (cfg->round > MAX_RDCU_ROUND) { + debug_print("Error: selected round parameter: %u is not supported. Largest supported value is: %u.\n", + cfg->round, MAX_RDCU_ROUND); + cfg_invalid++; + } + +#ifdef SKIP_CMP_PAR_CHECK + return 0; +#endif + + return -cfg_invalid; +} + + +/** + * @brief create a RDCU compression configuration + * + * @param data_type compression data product types + * @param cmp_mode compression mode + * @param model_value model weighting parameter (only need for model compression mode) + * @param lossy_par lossy rounding parameter (use CMP_LOSSLESS for lossless compression) + * + * @returns compression configuration containing the chosen parameters; + * on error the data_type record is set to DATA_TYPE_UNKOWN + */ + +struct cmp_cfg rdcu_cfg_create(enum cmp_ent_data_type data_type, enum cmp_mode cmp_mode, + uint32_t model_value, uint32_t lossy_par) +{ + struct cmp_cfg cfg = {0}; + + cfg.data_type = data_type; + cfg.cmp_mode = cmp_mode; + cfg.model_value = model_value; + cfg.round = lossy_par; + + if (rdcu_cfg_gen_par_is_invalid(&cfg)) + cfg.data_type = DATA_TYPE_UNKOWN; + + return cfg; +} + /** * @brief check if a buffer is in inside the RDCU SRAM * @@ -99,6 +193,7 @@ static int in_sram_range(uint32_t addr, uint32_t size) * @returns 0 if buffers are not overlapping, otherwise buffer are * overlapping */ + static int buffers_overlap(uint32_t start_a, uint32_t end_a, uint32_t start_b, uint32_t end_b) { @@ -110,302 +205,373 @@ static int buffers_overlap(uint32_t start_a, uint32_t end_a, uint32_t start_b, /** - * @brief check if the compressor configuration is valid for a RDCU compression, - * see the user manual for more information (PLATO-UVIE-PL-UM-0001). + * @brief check if RDCU buffer settings are invalid * - * @param cfg configuration contains all parameters required for compression + * @param cfg a pointer to a compression configuration * - * @returns >= 0 on success, error otherwise + * @returns 0 if buffers configuration is valid, otherwise the configuration is + * invalid */ -int rdcu_cmp_cfg_valid(const struct cmp_cfg *cfg) +static int rdcu_cfg_buffers_is_invalid(const struct cmp_cfg *cfg) { int cfg_invalid = 0; - int cfg_warning = 0; - - if (cfg == NULL) - return -1; - - if (cfg->cmp_mode > MAX_RDCU_CMP_MODE) { - printf("Error: selected cmp_mode: %u is not supported. " - "Largest supported mode is: %lu.\n", cfg->cmp_mode, - MAX_RDCU_CMP_MODE); - cfg_invalid++; - } - - if (cfg->model_value > MAX_MODEL_VALUE) { - printf("Error: selected model_value: %u is invalid. " - "Largest supported value is: %lu.\n", cfg->model_value, - MAX_MODEL_VALUE); - cfg_invalid++; - } - - if (cfg->golomb_par < MIN_RDCU_GOLOMB_PAR|| - cfg->golomb_par > MAX_RDCU_GOLOMB_PAR) { - printf("Error: The selected Golomb parameter: %u is not supported. " - "The Golomb parameter has to be between [%lu, %lu].\n", - cfg->golomb_par, MIN_RDCU_GOLOMB_PAR, - MAX_RDCU_GOLOMB_PAR); - cfg_invalid++; - } - - if (cfg->ap1_golomb_par < MIN_RDCU_GOLOMB_PAR || - cfg->ap1_golomb_par > MAX_RDCU_GOLOMB_PAR) { - printf("Error: The selected adaptive 1 Golomb parameter: %u is not supported. " - "The Golomb parameter has to be between [%lu, %lu].\n", - cfg->ap1_golomb_par, MIN_RDCU_GOLOMB_PAR, - MAX_RDCU_GOLOMB_PAR); - cfg_invalid++; - } - if (cfg->ap2_golomb_par < MIN_RDCU_GOLOMB_PAR || - cfg->ap2_golomb_par > MAX_RDCU_GOLOMB_PAR) { - printf("Error: The selected adaptive 2 Golomb parameter: %u is not supported. " - "The Golomb parameter has to be between [%lu, %lu].\n", - cfg->ap2_golomb_par, MIN_RDCU_GOLOMB_PAR, - MAX_RDCU_GOLOMB_PAR); - cfg_invalid++; - } - - if (cfg->spill < MIN_RDCU_SPILL) { - printf("Error: The selected spillover threshold value: %u is too small. " - "Smallest possible spillover value is: %lu.\n", - cfg->spill, MIN_RDCU_SPILL); - cfg_invalid++; - } - - if (cfg->spill > get_max_spill(cfg->golomb_par, cfg->cmp_mode)) { - printf("Error: The selected spillover threshold value: %u is " - "too large for the selected Golomb parameter: %u, the " - "largest possible spillover value is: %u.\n", - cfg->spill, cfg->golomb_par, - get_max_spill(cfg->golomb_par, cfg->cmp_mode)); - cfg_invalid++; - } - - if (cfg->ap1_spill < MIN_RDCU_SPILL) { - printf("Error: The selected adaptive 1 spillover threshold " - "value: %u is too small. " - "Smallest possible spillover value is: %lu.\n", - cfg->ap1_spill, MIN_RDCU_SPILL); - cfg_invalid++; - } - - if (cfg->ap1_spill > get_max_spill(cfg->ap1_golomb_par, cfg->cmp_mode)) { - printf("Error: The selected adaptive 1 spillover threshold " - "value: %u is too large for the selected adaptive 1 " - "Golomb parameter: %u, the largest possible adaptive 1 " - "spillover value is: %u.\n", - cfg->ap1_spill, cfg->ap1_golomb_par, - get_max_spill(cfg->ap1_golomb_par, cfg->cmp_mode)); - cfg_invalid++; - } - - if (cfg->ap2_spill < MIN_RDCU_SPILL) { - printf("Error: The selected adaptive 2 spillover threshold " - "value: %u is too small." - "Smallest possible spillover value is: %lu.\n", - cfg->ap2_spill, MIN_RDCU_SPILL); - cfg_invalid++; - } - - if (cfg->ap2_spill > get_max_spill(cfg->ap2_golomb_par, cfg->cmp_mode)) { - printf("Error: The selected adaptive 2 spillover threshold " - "value: %u is too large for the selected adaptive 2 " - "Golomb parameter: %u, the largest possible adaptive 2 " - "spillover value is: %u.\n", - cfg->ap2_spill, cfg->ap2_golomb_par, - get_max_spill(cfg->ap2_golomb_par, cfg->cmp_mode)); - cfg_invalid++; - } - - if (cfg->round > MAX_RDCU_ROUND) { - printf("Error: selected round parameter: %u is not supported. " - "Largest supported value is: %lu.\n", - cfg->round, MAX_RDCU_ROUND); - cfg_invalid++; - } - - if (cfg->samples == 0) { - printf("Warning: The samples parameter is set to 0. No data will be compressed.\n"); - cfg_warning++; - } - - if (cfg->buffer_length == 0) { - printf("Error: The buffer_length is set to 0. There is no place " - "to store the compressed data.\n"); - cfg_invalid++; - } - - if (cfg->cmp_mode == MODE_RAW) { + if (cfg->cmp_mode == CMP_MODE_RAW) { if (cfg->buffer_length < cfg->samples) { - printf("buffer_length is smaller than samples parameter. " - "There is not enough space to copy the data in " - "RAW mode.\n"); + debug_print("rdcu_buffer_length is smaller than samples parameter. There is not enough space to copy the data in RAW mode.\n"); cfg_invalid++; } } - if (!cfg->input_buf) { - printf("Warning: The data to compress buffer is set to NULL. " - "No data will be transferred to the rdcu_data_adr in " - "the RDCU-SRAM.\n"); - cfg_warning++; - } - if (cfg->rdcu_data_adr & 0x3) { - printf("Error: The RDCU data to compress start address is not 4-Byte aligned.\n"); + debug_print("Error: The RDCU data to compress start address is not 4-Byte aligned.\n"); cfg_invalid++; } if (cfg->rdcu_buffer_adr & 0x3) { - printf("Error: The RDCU compressed data start address is not 4-Byte aligned.\n"); + debug_print("Error: The RDCU compressed data start address is not 4-Byte aligned.\n"); cfg_invalid++; } - if (!in_sram_range(cfg->rdcu_data_adr, cfg->samples * SAM2BYT)) { - printf("Error: The RDCU data to compress buffer is outside the RDCU SRAM address space.\n"); + if (!in_sram_range(cfg->rdcu_data_adr, cfg->samples * IMA_SAM2BYT)) { + debug_print("Error: The RDCU data to compress buffer is outside the RDCU SRAM address space.\n"); cfg_invalid++; } - if (!in_sram_range(cfg->rdcu_buffer_adr, cfg->buffer_length * SAM2BYT)) { - printf("Error: The RDCU compressed data buffer is outside the RDCU SRAM address space.\n"); + if (!in_sram_range(cfg->rdcu_buffer_adr, cfg->buffer_length * IMA_SAM2BYT)) { + debug_print("Error: The RDCU compressed data buffer is outside the RDCU SRAM address space.\n"); cfg_invalid++; } if (buffers_overlap(cfg->rdcu_data_adr, - cfg->rdcu_data_adr + cfg->samples * SAM2BYT, + cfg->rdcu_data_adr + cfg->samples * IMA_SAM2BYT, cfg->rdcu_buffer_adr, - cfg->rdcu_buffer_adr + cfg->buffer_length * SAM2BYT)) { - printf("Error: The RDCU data to compress buffer and the RDCU " - "compressed data buffer are overlapping.\n"); + cfg->rdcu_buffer_adr + cfg->buffer_length * IMA_SAM2BYT)) { + debug_print("Error: The RDCU data to compress buffer and the RDCU compressed data buffer are overlapping.\n"); cfg_invalid++; } if (model_mode_is_used(cfg->cmp_mode)) { - if (cfg->model_buf == cfg->input_buf) { - printf("Error: The model buffer (model_buf) and the data " - "to be compressed (input_buf) are equal."); + if (cfg->model_buf && cfg->model_buf == cfg->input_buf) { + debug_print("Error: The model buffer (model_buf) and the data to be compressed (input_buf) are equal."); cfg_invalid++; } - if (!cfg->model_buf) { - printf("Warning: The model buffer is set to NULL. No " - "model data will be transferred to the " - "rdcu_model_adr in the RDCU-SRAM.\n"); - cfg_warning++; - } - if (cfg->rdcu_model_adr & 0x3) { - printf("Error: The RDCU model start address is not 4-Byte aligned.\n"); + debug_print("Error: The RDCU model start address is not 4-Byte aligned.\n"); cfg_invalid++; } - if (!in_sram_range(cfg->rdcu_model_adr, cfg->samples * SAM2BYT)) { - printf("Error: The RDCU model buffer is outside the RDCU SRAM address space.\n"); + if (!in_sram_range(cfg->rdcu_model_adr, cfg->samples * IMA_SAM2BYT)) { + debug_print("Error: The RDCU model buffer is outside the RDCU SRAM address space.\n"); cfg_invalid++; } if (buffers_overlap( cfg->rdcu_model_adr, - cfg->rdcu_model_adr + cfg->samples * SAM2BYT, + cfg->rdcu_model_adr + cfg->samples * IMA_SAM2BYT, cfg->rdcu_data_adr, - cfg->rdcu_data_adr + cfg->samples * SAM2BYT)) { - printf("Error: The model buffer and the data to compress buffer are overlapping.\n"); + cfg->rdcu_data_adr + cfg->samples * IMA_SAM2BYT)) { + debug_print("Error: The model buffer and the data to compress buffer are overlapping.\n"); cfg_invalid++; } if (buffers_overlap( cfg->rdcu_model_adr, - cfg->rdcu_model_adr + cfg->samples * SAM2BYT, + cfg->rdcu_model_adr + cfg->samples * IMA_SAM2BYT, cfg->rdcu_buffer_adr, - cfg->rdcu_buffer_adr + cfg->buffer_length * SAM2BYT) - ){ - printf("Error: The model buffer and the compressed data buffer are overlapping.\n"); + cfg->rdcu_buffer_adr + cfg->buffer_length * IMA_SAM2BYT) + ) { + debug_print("Error: The model buffer and the compressed data buffer are overlapping.\n"); cfg_invalid++; } if (cfg->rdcu_model_adr != cfg->rdcu_new_model_adr) { if (cfg->rdcu_new_model_adr & 0x3) { - printf("Error: The RDCU updated model start address " - "(rdcu_new_model_adr) is not 4-Byte aligned.\n"); + debug_print("Error: The RDCU updated model start address (rdcu_new_model_adr) is not 4-Byte aligned.\n"); cfg_invalid++; } if (!in_sram_range(cfg->rdcu_new_model_adr, - cfg->samples * SAM2BYT)) { - printf("Error: The RDCU updated model buffer is " - "outside the RDCU SRAM address space.\n"); + cfg->samples * IMA_SAM2BYT)) { + debug_print("Error: The RDCU updated model buffer is outside the RDCU SRAM address space.\n"); cfg_invalid++; } if (buffers_overlap( cfg->rdcu_new_model_adr, - cfg->rdcu_new_model_adr + cfg->samples * SAM2BYT, + cfg->rdcu_new_model_adr + cfg->samples * IMA_SAM2BYT, cfg->rdcu_data_adr, - cfg->rdcu_data_adr + cfg->samples * SAM2BYT) - ){ - printf("Error: The updated model buffer and the data to " - "compress buffer are overlapping.\n"); + cfg->rdcu_data_adr + cfg->samples * IMA_SAM2BYT) + ) { + debug_print("Error: The updated model buffer and the data to compress buffer are overlapping.\n"); cfg_invalid++; } if (buffers_overlap( cfg->rdcu_new_model_adr, - cfg->rdcu_new_model_adr + cfg->samples * SAM2BYT, + cfg->rdcu_new_model_adr + cfg->samples * IMA_SAM2BYT, cfg->rdcu_buffer_adr, - cfg->rdcu_buffer_adr + cfg->buffer_length * SAM2BYT) - ){ - printf("Error: The updated model buffer and the compressed " - "data buffer are overlapping.\n"); + cfg->rdcu_buffer_adr + cfg->buffer_length * IMA_SAM2BYT) + ) { + debug_print("Error: The updated model buffer and the compressed data buffer are overlapping.\n"); cfg_invalid++; } if (buffers_overlap( cfg->rdcu_new_model_adr, - cfg->rdcu_new_model_adr + cfg->samples * SAM2BYT, + cfg->rdcu_new_model_adr + cfg->samples * IMA_SAM2BYT, cfg->rdcu_model_adr, - cfg->rdcu_model_adr + cfg->samples * SAM2BYT) - ){ - printf("Error: The updated model buffer and the " - "model buffer are overlapping.\n"); + cfg->rdcu_model_adr + cfg->samples * IMA_SAM2BYT) + ) { + debug_print("Error: The updated model buffer and the model buffer are overlapping.\n"); cfg_invalid++; } } } - if (cfg->icu_new_model_buf) { - printf("Warning: ICU updated model buffer is set. This " - "buffer is not used for an RDCU compression.\n"); - cfg_warning++; + if (cfg->icu_new_model_buf) + debug_print("Warning: ICU updated model buffer is set. This buffer is not used for an RDCU compression.\n"); + + if (cfg->icu_output_buf) + debug_print("Warning: ICU compressed data buffer is set. This buffer is not used for an RDCU compression.\n"); + +#ifdef SKIP_CMP_PAR_CHECK + return 0; +#endif + return -cfg_invalid; +} + + +/** + *@brief setup of the different data buffers for an RDCU compression + * + * @param cfg pointer to a compression configuration (created + * with the rdcu_cfg_create() function) + * @param data_to_compress pointer to the data to be compressed (if NULL no + * data transfer to the RDCU) + * @param data_samples length of the data to be compressed measured in + * 16-bit data samples (ignoring the multi entity header) + * @param model_of_data pointer to model data buffer (only needed for + * model compression mode, if NULL no model data + * transfer to the RDCU) + * @param rdcu_data_adr RDCU data to compress start address, the first + * data address in the RDCU SRAM + * @param rdcu_model_adr RDCU model start address, the first model address + * in the RDCU SRAM (only need for model compression mode) + * @param rdcu_new_model_adr RDCU new/updated model start address(can be the + * by the same as rdcu_model_adr for in-place model update) + * @param rdcu_buffer_adr RDCU compressed data start address, the first + * output data address in the RDCU SRAM + * @param rdcu_buffer_lenght length of the RDCU compressed data SRAM buffer + * in number of 16-bit samples + * @returns 0 if parameters are valid, non-zero if parameters are invalid + */ + +int rdcu_cfg_buffers(struct cmp_cfg *cfg, uint16_t *data_to_compress, + uint32_t data_samples, uint16_t *model_of_data, + uint32_t rdcu_data_adr, uint32_t rdcu_model_adr, + uint32_t rdcu_new_model_adr, uint32_t rdcu_buffer_adr, + uint32_t rdcu_buffer_lenght) +{ + if (!cfg) { + debug_print("Error: pointer to the compression configuration structure is NULL.\n"); + return -1; } - if (cfg->icu_output_buf) { - printf("Warning: ICU compressed data buffer is set. This " - "buffer is not used for an RDCU compression.\n"); - cfg_warning++; + cfg->input_buf = data_to_compress; + cfg->samples = data_samples; + cfg->model_buf = model_of_data; + cfg->rdcu_data_adr = rdcu_data_adr; + cfg->rdcu_model_adr = rdcu_model_adr; + cfg->rdcu_new_model_adr = rdcu_new_model_adr; + cfg->rdcu_buffer_adr = rdcu_buffer_adr; + cfg->buffer_length = rdcu_buffer_lenght; + + if (rdcu_cfg_buffers_is_invalid(cfg)) + return -1; + + return 0; +} + + +/** + * @brief check if the Golomb and spillover threshold parameter combination is + * invalid for a RDCU compression + * @note also checked the adaptive Golomb and spillover threshold parameter combinations + * + * @param cfg a pointer to a compression configuration + * + * @returns 0 if (adaptive) Golomb spill threshold parameter combinations are + * valid, otherwise the configuration is invalid + */ + +static int rdcu_cfg_imagette_is_invalid(const struct cmp_cfg *cfg) +{ + int cfg_invalid = 0; + + if (cfg->golomb_par < MIN_RDCU_GOLOMB_PAR || + cfg->golomb_par > MAX_RDCU_GOLOMB_PAR) { + debug_print("Error: The selected Golomb parameter: %u is not supported. The Golomb parameter has to be between [%u, %u].\n", + cfg->golomb_par, MIN_RDCU_GOLOMB_PAR, MAX_RDCU_GOLOMB_PAR); + cfg_invalid++; + } + + if (cfg->ap1_golomb_par < MIN_RDCU_GOLOMB_PAR || + cfg->ap1_golomb_par > MAX_RDCU_GOLOMB_PAR) { + debug_print("Error: The selected adaptive 1 Golomb parameter: %u is not supported. The Golomb parameter has to be between [%u, %u].\n", + cfg->ap1_golomb_par, MIN_RDCU_GOLOMB_PAR, MAX_RDCU_GOLOMB_PAR); + cfg_invalid++; + } + + if (cfg->ap2_golomb_par < MIN_RDCU_GOLOMB_PAR || + cfg->ap2_golomb_par > MAX_RDCU_GOLOMB_PAR) { + debug_print("Error: The selected adaptive 2 Golomb parameter: %u is not supported. The Golomb parameter has to be between [%u, %u].\n", + cfg->ap2_golomb_par, MIN_RDCU_GOLOMB_PAR, MAX_RDCU_GOLOMB_PAR); + cfg_invalid++; + } + + if (cfg->spill < MIN_RDCU_SPILL) { + debug_print("Error: The selected spillover threshold value: %u is too small. Smallest possible spillover value is: %u.\n", + cfg->spill, MIN_RDCU_SPILL); + cfg_invalid++; + } + + if (cfg->spill > get_max_spill(cfg->golomb_par, cfg->data_type)) { + debug_print("Error: The selected spillover threshold value: %u is too large for the selected Golomb parameter: %u, the largest possible spillover value is: %u.\n", + cfg->spill, cfg->golomb_par, get_max_spill(cfg->golomb_par, cfg->data_type)); + cfg_invalid++; + } + + if (cfg->ap1_spill < MIN_RDCU_SPILL) { + debug_print("Error: The selected adaptive 1 spillover threshold value: %u is too small. Smallest possible spillover value is: %u.\n", + cfg->ap1_spill, MIN_RDCU_SPILL); + cfg_invalid++; + } + + if (cfg->ap1_spill > get_max_spill(cfg->ap1_golomb_par, cfg->data_type)) { + debug_print("Error: The selected adaptive 1 spillover threshold value: %u is too large for the selected adaptive 1 Golomb parameter: %u, the largest possible adaptive 1 spillover value is: %u.\n", + cfg->ap1_spill, cfg->ap1_golomb_par, get_max_spill(cfg->ap1_golomb_par, cfg->data_type)); + cfg_invalid++; + } + + if (cfg->ap2_spill < MIN_RDCU_SPILL) { + debug_print("Error: The selected adaptive 2 spillover threshold value: %u is too small. Smallest possible spillover value is: %u.\n", + cfg->ap2_spill, MIN_RDCU_SPILL); + cfg_invalid++; + } + + if (cfg->ap2_spill > get_max_spill(cfg->ap2_golomb_par, cfg->data_type)) { + debug_print("Error: The selected adaptive 2 spillover threshold value: %u is too large for the selected adaptive 2 Golomb parameter: %u, the largest possible adaptive 2 spillover value is: %u.\n", + cfg->ap2_spill, cfg->ap2_golomb_par, get_max_spill(cfg->ap2_golomb_par, cfg->data_type)); + cfg_invalid++; } #ifdef SKIP_CMP_PAR_CHECK return 0; #endif - if (cfg_invalid) - return -cfg_invalid; - else - return cfg_warning; + + return -cfg_invalid; +} + + +/** + * @brief set up the configuration parameters for an RDCU imagette compression + * + * @param cfg pointer to a compression configuration (created + * with the rdcu_cfg_create() function) + * @param golomb_par imagette compression parameter (Golomb parameter) + * @param spillover_par imagette spillover threshold parameter + * @param ap1_golomb_par adaptive 1 imagette compression parameter (ap1_golomb parameter) + * @param ap1_spillover_par adaptive 1 imagette spillover threshold parameter + * @param ap2_golomb_par adaptive 2 imagette compression parameter (ap2_golomb parameter) + * @param ap2_spillover_par adaptive 1 imagette spillover threshold parameter + * + * @returns 0 if parameters are valid, non-zero if parameters are invalid + */ + +int rdcu_cfg_imagette(struct cmp_cfg *cfg, + uint32_t golomb_par, uint32_t spillover_par, + uint32_t ap1_golomb_par, uint32_t ap1_spillover_par, + uint32_t ap2_golomb_par, uint32_t ap2_spillover_par) +{ + if (!cfg) { + debug_print("Error: pointer to the compression configuration structure is NULL.\n"); + return -1; + } + + cfg->golomb_par = golomb_par; + cfg->spill = spillover_par; + cfg->ap1_golomb_par = ap1_golomb_par; + cfg->ap1_spill = ap1_spillover_par; + cfg->ap2_golomb_par = ap2_golomb_par; + cfg->ap2_spill = ap2_spillover_par; + + if (rdcu_cfg_imagette_is_invalid(cfg)) + return -1; + + return 0; +} + + +/** + * @brief check if the compressor configuration is invalid for a RDCU compression, + * see the user manual for more information (PLATO-UVIE-PL-UM-0001). + * + * @param cfg pointer to a compression configuration contains all parameters + * required for compression + * + * @returns 0 if parameters are valid, non-zero if parameters are invalid + */ + +int rdcu_cmp_cfg_is_invalid(const struct cmp_cfg *cfg) +{ + int cfg_invalid = 0; + + if (!cfg) { + debug_print("Error: pointer to the compression configuration structure is NULL.\n"); + return -1; + } + + if (!cfg->input_buf) + debug_print("Warning: The data to compress buffer is set to NULL. No data will be transferred to the rdcu_data_adr in the RDCU-SRAM.\n"); + + if (model_mode_is_used(cfg->cmp_mode)) { + if (!cfg->model_buf) + debug_print("Warning: The model buffer is set to NULL. No model data will be transferred to the rdcu_model_adr in the RDCU-SRAM.\n"); + } + + if (cfg->input_buf && cfg->samples == 0) + debug_print("Warning: The samples parameter is set to 0. No data will be compressed.\n"); + + if (cfg->buffer_length == 0) { + debug_print("Error: The buffer_length is set to 0. There is no place to store the compressed data.\n"); + cfg_invalid++; + } + + if (rdcu_cfg_gen_par_is_invalid(cfg)) + cfg_invalid++; + if (rdcu_cfg_buffers_is_invalid(cfg)) + cfg_invalid++; + if (rdcu_cfg_imagette_is_invalid(cfg)) + cfg_invalid++; + + return -cfg_invalid; } /** * @brief set up RDCU compression register * - * @param cfg configuration contains all parameters required for compression + * @param cfg pointer to a compression configuration contains all parameters + * required for compression * * @returns 0 on success, error otherwise */ int rdcu_set_compression_register(const struct cmp_cfg *cfg) { - if (rdcu_cmp_cfg_valid(cfg) < 0) + if (rdcu_cmp_cfg_is_invalid(cfg)) return -1; /* first, set compression parameters in local mirror registers */ @@ -509,8 +675,6 @@ int rdcu_start_compression(void) * * @note when using the 1d-differencing mode or the raw mode (cmp_mode = 0,2,4), * the model parameters (model_value, model_buf, rdcu_model_adr) are ignored - * @note the icu_output_buf will not be used for the RDCU compression - * @note the overlapping of the different rdcu buffers is not checked * @note the validity of the cfg structure is checked before the compression is * started * @@ -700,7 +864,7 @@ int rdcu_read_model(const struct cmp_info *info, void *model_buf) return -1; /* calculate the need bytes for the model */ - s = cmp_cal_size_of_data(info->samples_used, info->cmp_mode_used); + s = info->samples_used * IMA_SAM2BYT; if (model_buf == NULL) return (int)s; @@ -790,19 +954,17 @@ int rdcu_compress_data_parallel(const struct cmp_cfg *cfg, if (last_info->cmp_err) return -1; - /* TODO: check read write buffer overlapping */ - rdcu_set_compression_register(cfg); /* round up needed size must be a multiple of 4 bytes */ - samples_4byte = (cfg->samples * SAM2BYT + 3) & ~3U; + samples_4byte = (cfg->samples * IMA_SAM2BYT + 3) & ~3U; if (cfg->input_buf != NULL) { uint32_t cmp_size_4byte; /* now set the data in the local mirror... */ if (rdcu_write_sram_16(cfg->input_buf, cfg->rdcu_data_adr, - cfg->samples * SAM2BYT) < 0) + cfg->samples * IMA_SAM2BYT) < 0) return -1; /* calculate the need bytes for the bitstream */ @@ -823,7 +985,7 @@ int rdcu_compress_data_parallel(const struct cmp_cfg *cfg, return -1; } } else { - printf("Warning: input_buf = NULL; input_buf is not written to the sram and compressed data is not read from the sram\n"); + debug_print("Warning: input_buf = NULL; input_buf is not written to the sram and compressed data is not read from the SRAM\n"); } /* read model and write model in parallel */ @@ -832,10 +994,10 @@ int rdcu_compress_data_parallel(const struct cmp_cfg *cfg, /* set the model in the local mirror... */ if (rdcu_write_sram_16(cfg->model_buf, cfg->rdcu_model_adr, - cfg->samples * SAM2BYT) < 0) + cfg->samples * IMA_SAM2BYT) < 0) return -1; - new_model_size_4byte = last_info->samples_used * SAM2BYT ; + new_model_size_4byte = last_info->samples_used * IMA_SAM2BYT; if (rdcu_sync_sram_mirror_parallel(last_info->rdcu_new_model_adr_used, (new_model_size_4byte+3) & ~0x3U, cfg->rdcu_model_adr, @@ -853,7 +1015,7 @@ int rdcu_compress_data_parallel(const struct cmp_cfg *cfg, } else if (cfg->model_buf && model_mode_is_used(cfg->cmp_mode)) { /* set the model in the local mirror... */ if (rdcu_write_sram_16(cfg->model_buf, cfg->rdcu_model_adr, - cfg->samples * SAM2BYT) < 0) + cfg->samples * IMA_SAM2BYT) < 0) return -1; if (rdcu_sync_mirror_to_sram(cfg->rdcu_model_adr, samples_4byte, diff --git a/lib/cmp_support.c b/lib/cmp_support.c index 93db7b6..a02e897 100644 --- a/lib/cmp_support.c +++ b/lib/cmp_support.c @@ -18,75 +18,15 @@ */ -#include "../include/cmp_support.h" -#include "../include/cmp_data_types.h" -#include "../include/cmp_debug.h" - - -/** - * @brief Default configuration of the Compressor in model imagette mode. - * @warning All ICU buffers are set to NULL. Samples and buffer_length are set to 0 - * @note see PLATO-IWF-PL-RS-0005 V1.1 - */ - - -const struct cmp_cfg DEFAULT_CFG_MODEL = { - MODE_MODEL_MULTI, /* cmp_mode */ - 4, /* golomb_par */ - 48, /* spill */ - 8, /* model_value */ - 0, /* round */ - 3, /* ap1_golomb_par */ - 35, /* ap1_spill */ - 5, /* ap2_golomb_par */ - 60, /* ap2_spill */ - NULL, /* *input_buf */ - 0x000000, /* rdcu_data_adr */ - NULL, /* *model_buf */ - 0x200000, /* rdcu_model_adr */ - NULL, /* *icu_new_model_buf */ - 0x400000, /* rdcu_up_model_adr */ - 0, /* samples */ - NULL, /* *icu_output_buf */ - 0x600000, /* rdcu_buffer_adr */ - 0x0 /* buffer_length */ -}; - - -/** - * @brief Default configuration of the Compressor in 1d-differencing imagette mode. - * @warning All ICU buffers are set to NULL. Samples and buffer_length are set to 0 - * @note see PLATO-IWF-PL-RS-0005 V1.1 - */ - -const struct cmp_cfg DEFAULT_CFG_DIFF = { - MODE_DIFF_ZERO, /* cmp_mode */ - 7, /* golomb_par */ - 60, /* spill */ - 8, /* model_value */ - 0, /* round */ - 6, /* ap1_golomb_par */ - 48, /* ap1_spill */ - 8, /* ap2_golomb_par */ - 72, /* ap2_spill */ - NULL, /* *input_buf */ - 0x000000, /* rdcu_data_adr */ - NULL, /* *model_buf */ - 0x000000, /* rdcu_model_adr */ - NULL, /* *icu_new_model_buf */ - 0x000000, /* rdcu_up_model_adr */ - 0, /* samples */ - NULL, /* *icu_output_buf */ - 0x600000, /* rdcu_buffer_adr */ - 0x0 /* buffer_length */ -}; +#include "cmp_support.h" +#include "cmp_debug.h" /** * @brief implementation of the logarithm base of floor(log2(x)) for integers * @note ilog_2(0) = -1 defined * - * @param x input parameter + * @param x input parameter * * @returns the result of floor(log2(x)) */ @@ -101,10 +41,10 @@ int ilog_2(uint32_t x) /** - * @brief Determining if an integer is a power of 2 + * @brief determining if an integer is a power of 2 * @note 0 is incorrectly considered a power of 2 here * - * @param v we want to see if v is a power of 2 + * @param v we want to see if v is a power of 2 * * @returns 1 if v is a power of 2, otherwise 0 * @@ -117,231 +57,278 @@ int is_a_pow_of_2(unsigned int v) } +/** + * @brief check if the compression entity data product type is supported + * + * @param data_type compression entity data product type to check + * + * @returns zero if data_type is invalid; non-zero if data_type is valid + */ + +int cmp_data_type_valid(enum cmp_data_type data_type) +{ + if (data_type <= DATA_TYPE_UNKOWN || data_type > DATA_TYPE_F_CAM_OFFSET) + return 0; + + return 1; +} + + /** * @brief check if a model mode is selected * - * @param cmp_mode compression mode + * @param cmp_mode compression mode * - * @returns 1 when model mode is set, otherwise 0 + * @returns 1 when the model mode is used, otherwise 0 */ -int model_mode_is_used(unsigned int cmp_mode) +int model_mode_is_used(enum cmp_mode cmp_mode) { - switch (cmp_mode) { - case MODE_MODEL_ZERO: - case MODE_MODEL_ZERO_S_FX: - case MODE_MODEL_ZERO_S_FX_EFX: - case MODE_MODEL_ZERO_S_FX_NCOB: - case MODE_MODEL_ZERO_S_FX_EFX_NCOB_ECOB: - case MODE_MODEL_ZERO_F_FX: - case MODE_MODEL_ZERO_F_FX_EFX: - case MODE_MODEL_ZERO_F_FX_NCOB: - case MODE_MODEL_ZERO_F_FX_EFX_NCOB_ECOB: - case MODE_MODEL_MULTI: - case MODE_MODEL_MULTI_S_FX: - case MODE_MODEL_MULTI_S_FX_EFX: - case MODE_MODEL_MULTI_S_FX_NCOB: - case MODE_MODEL_MULTI_S_FX_EFX_NCOB_ECOB: - case MODE_MODEL_MULTI_F_FX: - case MODE_MODEL_MULTI_F_FX_EFX: - case MODE_MODEL_MULTI_F_FX_NCOB: - case MODE_MODEL_MULTI_F_FX_EFX_NCOB_ECOB: - case MODE_MODEL_ZERO_32: - case MODE_MODEL_MULTI_32: + if (cmp_mode == CMP_MODE_MODEL_ZERO || + cmp_mode == CMP_MODE_MODEL_MULTI) return 1; - break; - default: - return 0; - break; - } + + return 0; } /** * @brief check if a 1d-differencing mode is selected * - * @param cmp_mode compression mode + * @param cmp_mode compression mode * - * @returns 1 when 1d-differencing mode is set, otherwise 0 + * @returns 1 when the 1d-differencing mode is used, otherwise 0 */ -int diff_mode_is_used(unsigned int cmp_mode) +int diff_mode_is_used(enum cmp_mode cmp_mode) +{ + if (cmp_mode == CMP_MODE_DIFF_ZERO || + cmp_mode == CMP_MODE_DIFF_MULTI) + return 1; + + return 0; +} + + +/** + * @brief check if the raw mode is selected + * + * @param cmp_mode compression mode + * + * @returns 1 when the raw mode is used, otherwise 0 + */ + +int raw_mode_is_used(enum cmp_mode cmp_mode) +{ + if (cmp_mode == CMP_MODE_RAW) + return 1; + + return 0; +} + + +/** + * @brief check if the compression mode is supported by the RDCU compressor + * + * @param cmp_mode compression mode + * + * @returns 1 when the compression mode is supported by the RDCU, otherwise 0 + */ + +int rdcu_supported_cmp_mode_is_used(enum cmp_mode cmp_mode) { switch (cmp_mode) { - case MODE_DIFF_ZERO: - case MODE_DIFF_ZERO_S_FX: - case MODE_DIFF_ZERO_S_FX_EFX: - case MODE_DIFF_ZERO_S_FX_NCOB: - case MODE_DIFF_ZERO_S_FX_EFX_NCOB_ECOB: - case MODE_DIFF_ZERO_F_FX: - case MODE_DIFF_ZERO_F_FX_EFX: - case MODE_DIFF_ZERO_F_FX_NCOB: - case MODE_DIFF_ZERO_F_FX_EFX_NCOB_ECOB: - case MODE_DIFF_MULTI: - case MODE_DIFF_MULTI_S_FX: - case MODE_DIFF_MULTI_S_FX_EFX: - case MODE_DIFF_MULTI_S_FX_NCOB: - case MODE_DIFF_MULTI_S_FX_EFX_NCOB_ECOB: - case MODE_DIFF_MULTI_F_FX: - case MODE_DIFF_MULTI_F_FX_EFX: - case MODE_DIFF_MULTI_F_FX_NCOB: - case MODE_DIFF_MULTI_F_FX_EFX_NCOB_ECOB: - case MODE_DIFF_ZERO_32: - case MODE_DIFF_MULTI_32: + case CMP_MODE_RAW: + case CMP_MODE_MODEL_ZERO: + case CMP_MODE_DIFF_ZERO: + case CMP_MODE_MODEL_MULTI: + case CMP_MODE_DIFF_MULTI: return 1; - break; default: return 0; - break; } + } /** - * @brief check if the raw mode is selected + * @brief check if the data product data type is supported by the RDCU compressor * - * @param cmp_mode compression mode + * @param data_type compression data product types * - * @returns 1 when raw mode is set, otherwise 0 + * @returns 1 when the data type is supported by the RDCU, otherwise 0 */ -int raw_mode_is_used(unsigned int cmp_mode) +int rdcu_supported_data_type_is_used(enum cmp_data_type data_type) { - switch (cmp_mode) { - case MODE_RAW: - case MODE_RAW_S_FX: - case MODE_RAW_32: + switch (data_type) { + case DATA_TYPE_IMAGETTE: + case DATA_TYPE_IMAGETTE_ADAPTIVE: + case DATA_TYPE_SAT_IMAGETTE: + case DATA_TYPE_SAT_IMAGETTE_ADAPTIVE: + case DATA_TYPE_F_CAM_IMAGETTE: + case DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE: return 1; - break; default: return 0; - break; } } /** - * @brief check if the mode is supported by the RDCU compressor + * @brief check if the compression mode is supported for an ICU compression * - * @param cmp_mode compression mode + * @param cmp_mode compression mode * - * @returns 1 when mode is supported by the RDCU, otherwise 0 + * @returns 1 when the compression mode is supported, otherwise 0 */ -int rdcu_supported_mode_is_used(unsigned int cmp_mode) +int cmp_mode_is_supported(enum cmp_mode cmp_mode) { switch (cmp_mode) { - case MODE_RAW: - case MODE_MODEL_ZERO: - case MODE_DIFF_ZERO: - case MODE_MODEL_MULTI: - case MODE_DIFF_MULTI: + case CMP_MODE_RAW: + case CMP_MODE_MODEL_ZERO: + case CMP_MODE_DIFF_ZERO: + case CMP_MODE_MODEL_MULTI: + case CMP_MODE_DIFF_MULTI: + case CMP_MODE_STUFF: + return 1; + } + return 0; +} + + +/** + * @brief check if zero escape symbol mechanism mode is used + * + * @param cmp_mode compression mode + * + * @returns 1 when zero escape symbol mechanism is set, otherwise 0 + */ + +int zero_escape_mech_is_used(enum cmp_mode cmp_mode) +{ + if (cmp_mode == CMP_MODE_MODEL_ZERO || + cmp_mode == CMP_MODE_DIFF_ZERO) + return 1; + + return 0; +} + + +/** + * @brief check if multi escape symbol mechanism mode is used + * + * @param cmp_mode compression mode + * + * @returns 1 when multi escape symbol mechanism is set, otherwise 0 + */ + +int multi_escape_mech_is_used(enum cmp_mode cmp_mode) +{ + if (cmp_mode == CMP_MODE_MODEL_MULTI || + cmp_mode == CMP_MODE_DIFF_MULTI) + return 1; + + return 0; +} + + +/** + * @brief check if an imagette compression data type is used + * @note adaptive imagette compression data types included + * + * @param data_type compression data type + * + * @returns 1 when data_type is an imagette data type, otherwise 0 + */ + +int cmp_imagette_data_type_is_used(enum cmp_data_type data_type) +{ + switch (data_type) { + case DATA_TYPE_IMAGETTE: + case DATA_TYPE_IMAGETTE_ADAPTIVE: + case DATA_TYPE_SAT_IMAGETTE: + case DATA_TYPE_SAT_IMAGETTE_ADAPTIVE: + case DATA_TYPE_F_CAM_IMAGETTE: + case DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE: return 1; - break; default: return 0; - break; } } /** - * @brief check if the mode is available + * @brief check if an adaptive imagette compression data type is used * - * @param cmp_mode compression mode + * @param data_type compression data type * - * @returns 1 when mode is available, otherwise 0 + * @returns 1 when data_type is an adaptive imagette data type, otherwise 0 */ -int cmp_mode_available(unsigned int cmp_mode) +int cmp_ap_imagette_data_type_is_used(enum cmp_data_type data_type) { - if (diff_mode_is_used(cmp_mode) || - model_mode_is_used(cmp_mode) || - raw_mode_is_used(cmp_mode)) + switch (data_type) { + case DATA_TYPE_IMAGETTE_ADAPTIVE: + case DATA_TYPE_SAT_IMAGETTE_ADAPTIVE: + case DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE: return 1; - else + default: return 0; - + } } /** - * @brief check if zero escape symbol mechanism mode is used + * @brief check if a flux/center of brightness compression data type is used * - * @param cmp_mode compression mode + * @param data_type compression data type * - * @returns 1 when zero escape symbol mechanism is set, otherwise 0 + * @returns 1 when data_type is a flux/center of brightness data type, otherwise 0 */ -int zero_escape_mech_is_used(unsigned int cmp_mode) +int cmp_fx_cob_data_type_is_used(enum cmp_data_type data_type) { - switch (cmp_mode) { - case MODE_MODEL_ZERO: - case MODE_DIFF_ZERO: - case MODE_MODEL_ZERO_S_FX: - case MODE_DIFF_ZERO_S_FX: - case MODE_MODEL_ZERO_S_FX_EFX: - case MODE_DIFF_ZERO_S_FX_EFX: - case MODE_MODEL_ZERO_S_FX_NCOB: - case MODE_DIFF_ZERO_S_FX_NCOB: - case MODE_MODEL_ZERO_S_FX_EFX_NCOB_ECOB: - case MODE_DIFF_ZERO_S_FX_EFX_NCOB_ECOB: - case MODE_MODEL_ZERO_F_FX: - case MODE_DIFF_ZERO_F_FX: - case MODE_MODEL_ZERO_F_FX_EFX: - case MODE_DIFF_ZERO_F_FX_EFX: - case MODE_MODEL_ZERO_F_FX_NCOB: - case MODE_DIFF_ZERO_F_FX_NCOB: - case MODE_MODEL_ZERO_F_FX_EFX_NCOB_ECOB: - case MODE_DIFF_ZERO_F_FX_EFX_NCOB_ECOB: - case MODE_MODEL_ZERO_32: - case MODE_DIFF_ZERO_32: + switch (data_type) { + case DATA_TYPE_S_FX: + case DATA_TYPE_S_FX_DFX: + case DATA_TYPE_S_FX_NCOB: + case DATA_TYPE_S_FX_DFX_NCOB_ECOB: + case DATA_TYPE_L_FX: + case DATA_TYPE_L_FX_DFX: + case DATA_TYPE_L_FX_NCOB: + case DATA_TYPE_L_FX_DFX_NCOB_ECOB: + case DATA_TYPE_F_FX: + case DATA_TYPE_F_FX_DFX: + case DATA_TYPE_F_FX_NCOB: + case DATA_TYPE_F_FX_DFX_NCOB_ECOB: return 1; - break; default: return 0; - break; } - } /** - * @brief check if multi escape symbol mechanism mode is used + * @brief check if an auxiliary science compression data type is used * - * @param cmp_mode compression mode + * @param data_type compression data type * - * @returns 1 when multi escape symbol mechanism is set, otherwise 0 + * @returns 1 when data_type is an auxiliary science data type, otherwise 0 */ -int multi_escape_mech_is_used(unsigned int cmp_mode) +int cmp_aux_data_type_is_used(enum cmp_data_type data_type) { - switch (cmp_mode) { - case MODE_MODEL_MULTI: - case MODE_DIFF_MULTI: - case MODE_MODEL_MULTI_S_FX: - case MODE_DIFF_MULTI_S_FX: - case MODE_MODEL_MULTI_S_FX_EFX: - case MODE_DIFF_MULTI_S_FX_EFX: - case MODE_MODEL_MULTI_S_FX_NCOB: - case MODE_DIFF_MULTI_S_FX_NCOB: - case MODE_MODEL_MULTI_S_FX_EFX_NCOB_ECOB: - case MODE_DIFF_MULTI_S_FX_EFX_NCOB_ECOB: - case MODE_MODEL_MULTI_F_FX: - case MODE_DIFF_MULTI_F_FX: - case MODE_MODEL_MULTI_F_FX_EFX: - case MODE_DIFF_MULTI_F_FX_EFX: - case MODE_MODEL_MULTI_F_FX_NCOB: - case MODE_DIFF_MULTI_F_FX_NCOB: - case MODE_MODEL_MULTI_F_FX_EFX_NCOB_ECOB: - case MODE_DIFF_MULTI_F_FX_EFX_NCOB_ECOB: - case MODE_MODEL_MULTI_32: - case MODE_DIFF_MULTI_32: + switch (data_type) { + case DATA_TYPE_OFFSET: + case DATA_TYPE_BACKGROUND: + case DATA_TYPE_SMEARING: + case DATA_TYPE_F_CAM_OFFSET: + case DATA_TYPE_F_CAM_BACKGROUND: return 1; - break; default: return 0; - break; } } @@ -349,8 +336,8 @@ int multi_escape_mech_is_used(unsigned int cmp_mode) /** * @brief method for lossy rounding * - * @param value value to round - * @param round round parameter + * @param value the value to round + * @param round rounding parameter * * @return rounded value */ @@ -364,8 +351,8 @@ unsigned int round_fwd(unsigned int value, unsigned int round) /** * @brief inverse method for lossy rounding * - * @param value value to round back - * @param round round parameter + * @param value the value to round back + * @param round rounding parameter * * @return back rounded value */ @@ -378,9 +365,8 @@ unsigned int round_inv(unsigned int value, unsigned int round) /** * @brief implantation of the model update equation - * * @note check before that model_value is not greater than MAX_MODEL_VALUE - + * * @param data data to process * @param model (current) model of the data to process * @param model_value model weighting parameter @@ -388,9 +374,14 @@ unsigned int round_inv(unsigned int value, unsigned int round) * @returns (new) updated model */ -unsigned int cal_up_model(unsigned int data, unsigned int model, unsigned int - model_value) +unsigned int cmp_up_model(unsigned int data, unsigned int model, + unsigned int model_value, unsigned int round) + { + /* round and round back input because for decompression the accurate + * data values are not available + */ + data = round_inv(round_fwd(data, round), round); /* cast uint64_t to prevent overflow in the multiplication */ uint64_t weighted_model = (uint64_t)model * model_value; uint64_t weighted_data = (uint64_t)data * (MAX_MODEL_VALUE - model_value); @@ -402,13 +393,13 @@ unsigned int cal_up_model(unsigned int data, unsigned int model, unsigned int /** * @brief get the maximum valid spill threshold value for a given golomb_par * - * @param golomb_par Golomb parameter - * @param cmp_mode compression mode + * @param golomb_par Golomb parameter + * @param data_type compression data type * * @returns the highest still valid spill threshold value */ -uint32_t get_max_spill(unsigned int golomb_par, unsigned int cmp_mode) +uint32_t get_max_spill(unsigned int golomb_par, enum cmp_data_type data_type) { const uint32_t LUT_MAX_RDCU[MAX_RDCU_GOLOMB_PAR+1] = { 0, 8, 22, 35, 48, 60, 72, 84, 96, 107, 118, 129, 140, 151, 162, 173, 184, 194, @@ -420,208 +411,447 @@ uint32_t get_max_spill(unsigned int golomb_par, unsigned int cmp_mode) if (golomb_par == 0) return 0; - if (rdcu_supported_mode_is_used(cmp_mode)) { + /* the RDCU can only generate 16 bit long code words -> lower max spill needed */ + if (rdcu_supported_data_type_is_used(data_type)) { if (golomb_par > MAX_RDCU_GOLOMB_PAR) return 0; return LUT_MAX_RDCU[golomb_par]; + } + + if (golomb_par > MAX_ICU_GOLOMB_PAR) { + return 0; } else { - if (golomb_par > MAX_ICU_GOLOMB_PAR) { - return 0; - } else { - /* the ICU compressor can generate code words with a length of - * maximal 32 bits. */ - unsigned int max_cw_bits = 32; - unsigned int cutoff = (1UL << (ilog_2(golomb_par)+1)) - golomb_par; - unsigned int max_n_sym_offset = max_cw_bits/2 - 1; - return (max_cw_bits-1-ilog_2(golomb_par))*golomb_par + cutoff - - max_n_sym_offset - 1; + /* the ICU compressor can generate code words with a length of + * maximal 32 bits. + */ + unsigned int max_cw_bits = 32; + unsigned int cutoff = (1UL << (ilog_2(golomb_par)+1)) - golomb_par; + unsigned int max_n_sym_offset = max_cw_bits/2 - 1; + + return (max_cw_bits-1-ilog_2(golomb_par))*golomb_par + cutoff - + max_n_sym_offset - 1; + } +} + + +/** + * @brief calculate the need bytes to hold a bitstream + * + * @param cmp_size_bit compressed data size, measured in bits + * + * @returns the size in bytes to store the hole bitstream + * @note we round up the result to multiples of 4 bytes + */ + +unsigned int cmp_bit_to_4byte(unsigned int cmp_size_bit) +{ + return (((cmp_size_bit + 7) / 8) + 3) & ~0x3UL; +} + + +/** + * @brief check if the compression data type, compression mode, model value and + * the lossy rounding parameters are valid for a ICU compression + * + * @param cfg pointer to the compressor configuration + * + * @returns 1 if generic compression parameters are valid, otherwise 0 + */ + +int cmp_cfg_icu_gen_par_is_valid(const struct cmp_cfg *cfg) +{ + int cfg_invalid = 0; + + if (!cmp_data_type_valid(cfg->data_type)) { + debug_print("Error: selected compression data type is not supported.\n"); + cfg_invalid++; + } + + if (cfg->cmp_mode > CMP_MODE_STUFF) { + debug_print("Error: selected cmp_mode: %u is not supported\n.", cfg->cmp_mode); + cfg_invalid++; + } + + if (model_mode_is_used(cfg->cmp_mode)) { + if (cfg->model_value > MAX_MODEL_VALUE) { + debug_print("Error: selected model_value: %u is invalid. Largest supported value is: %u.\n", + cfg->model_value, MAX_MODEL_VALUE); + cfg_invalid++; } } + + if (cfg->round > MAX_ICU_ROUND) { + debug_print("Error: selected lossy parameter: %u is not supported. Largest supported value is: %u.\n", + cfg->round, MAX_ICU_ROUND); + cfg_invalid++; + } + + if (cfg_invalid) + return 0; + + return 1; } /** - * @brief get a good spill threshold parameter for the selected Golomb parameter - * and compression mode + * @brief check if the buffer parameters are valid * - * @param golomb_par Golomb parameter - * @param cmp_mode compression mode + * @param cfg pointer to the compressor configuration * - * @returns a good spill parameter (optimal for zero escape mechanism) - * @warning icu compression not support yet! + * @returns 1 if the buffer parameters are valid, otherwise 0 */ -uint32_t cmp_get_good_spill(unsigned int golomb_par, unsigned int cmp_mode) +int cmp_cfg_icu_buffers_is_valid(const struct cmp_cfg *cfg) { - const uint32_t LUT_RDCU_MULIT[MAX_RDCU_GOLOMB_PAR+1] = {0, 8, 16, 23, - 30, 36, 44, 51, 58, 64, 71, 77, 84, 90, 97, 108, 115, 121, 128, - 135, 141, 148, 155, 161, 168, 175, 181, 188, 194, 201, 207, 214, - 229, 236, 242, 250, 256, 263, 269, 276, 283, 290, 296, 303, 310, - 317, 324, 330, 336, 344, 351, 358, 363, 370, 377, 383, 391, 397, - 405, 411, 418, 424, 431, 452 }; + int cfg_invalid = 0; - if (zero_escape_mech_is_used(cmp_mode)) - return get_max_spill(golomb_par, cmp_mode); + if (!cfg) + return 0; - if (cmp_mode == MODE_MODEL_MULTI) { - if (golomb_par > MAX_RDCU_GOLOMB_PAR) - return 0; - else - return LUT_RDCU_MULIT[golomb_par]; + if (cfg->input_buf == NULL) { + debug_print("Error: The data_to_compress buffer for the data to be compressed is NULL.\n"); + cfg_invalid++; } - if (cmp_mode == MODE_DIFF_MULTI) - return CMP_GOOD_SPILL_DIFF_MULTI; + if (cfg->samples == 0) + debug_print("Warning: The samples parameter is 0. No data are compressed. This behavior may not be intended.\n"); - return 0; + if (cfg->icu_output_buf && cfg->buffer_length == 0 && cfg->samples != 0) { + debug_print("Error: The buffer_length is set to 0. There is no space to store the compressed data.\n"); + cfg_invalid++; + } + + if (cfg->icu_output_buf == cfg->input_buf) { + debug_print("Error: The compressed_data buffer is the same as the data_to_compress buffer.\n"); + cfg_invalid++; + } + + if (model_mode_is_used(cfg->cmp_mode)) { + if (cfg->model_buf == NULL) { + debug_print("Error: The model_of_data buffer for the model data is NULL.\n"); + cfg_invalid++; + } + + if (cfg->model_buf == cfg->input_buf) { + debug_print("Error: The model_of_data buffer is the same as the data_to_compress buffer.\n"); + cfg_invalid++; + } + + if (cfg->model_buf == cfg->icu_output_buf) { + debug_print("Error: The model_of_data buffer is the same as the compressed_data buffer.\n"); + cfg_invalid++; + } + + if (cfg->icu_new_model_buf) { + if (cfg->icu_new_model_buf == cfg->input_buf) { + debug_print("Error: The updated_model buffer is the same as the data_to_compress buffer.\n"); + cfg_invalid++; + } + + if (cfg->icu_new_model_buf == cfg->icu_output_buf) { + debug_print("Error: The compressed_data buffer is the same as the compressed_data buffer.\n"); + cfg_invalid++; + } + } + } + + if (raw_mode_is_used(cfg->cmp_mode)) { + if (cfg->buffer_length < cfg->samples) { + debug_print("Error: The compressed_data_len_samples is to small to hold the data form the data_to_compress.\n"); + cfg_invalid++; + } + } else { + if (cfg->samples < cfg->buffer_length/3) + debug_print("Warning: The size of the compressed_data buffer is 3 times smaller than the data_to_compress. This is probably unintended.This is probably unintended.\n"); + } + + if (cfg_invalid) + return 0; + + return 1; } /** - * @brief calculate the size of a sample for the different compression modes + * @brief check if the combination of the different compression parameters is valid * - * @param cmp_mode compression mode + * @param cmp_par compression parameter + * @param spill spillover threshold parameter + * @param cmp_mode compression mode + * @param data_type compression data type + * @param par_name string describing the use of the compression par. for + * debug messages (can be NULL) * - * @returns the size of a data sample in bytes for the selected compression - * mode; + * @returns 1 if the parameter combination is valid, otherwise 0 */ -size_t size_of_a_sample(unsigned int cmp_mode) +static int cmp_pars_are_valid(uint32_t cmp_par, uint32_t spill, enum cmp_mode cmp_mode, + enum cmp_data_type data_type, char *par_name) { - size_t sample_len; + int cfg_invalid = 0; + + if (!par_name) + par_name = ""; switch (cmp_mode) { - case MODE_RAW: - case MODE_MODEL_ZERO: - case MODE_MODEL_MULTI: - case MODE_DIFF_ZERO: - case MODE_DIFF_MULTI: - sample_len = sizeof(uint16_t); - break; - case MODE_RAW_S_FX: - case MODE_MODEL_ZERO_S_FX: - case MODE_MODEL_MULTI_S_FX: - case MODE_DIFF_ZERO_S_FX: - case MODE_DIFF_MULTI_S_FX: - sample_len = sizeof(struct S_FX); - break; - case MODE_MODEL_ZERO_S_FX_EFX: - case MODE_MODEL_MULTI_S_FX_EFX: - case MODE_DIFF_ZERO_S_FX_EFX: - case MODE_DIFF_MULTI_S_FX_EFX: - sample_len = sizeof(struct S_FX_NCOB); - break; - case MODE_MODEL_ZERO_S_FX_EFX_NCOB_ECOB: - case MODE_MODEL_MULTI_S_FX_EFX_NCOB_ECOB: - case MODE_DIFF_ZERO_S_FX_EFX_NCOB_ECOB: - case MODE_DIFF_MULTI_S_FX_EFX_NCOB_ECOB: - sample_len = sizeof(struct S_FX_EFX_NCOB_ECOB); + case CMP_MODE_RAW: + /* no checks needed */ break; - case MODE_MODEL_ZERO_F_FX: - case MODE_MODEL_MULTI_F_FX: - case MODE_DIFF_ZERO_F_FX: - case MODE_DIFF_MULTI_F_FX: - sample_len = sizeof(struct F_FX); - break; - case MODE_MODEL_ZERO_F_FX_EFX: - case MODE_MODEL_MULTI_F_FX_EFX: - case MODE_DIFF_ZERO_F_FX_EFX: - case MODE_DIFF_MULTI_F_FX_EFX: - sample_len = sizeof(struct F_FX_EFX); - break; - case MODE_MODEL_ZERO_F_FX_NCOB: - case MODE_MODEL_MULTI_F_FX_NCOB: - case MODE_DIFF_ZERO_F_FX_NCOB: - case MODE_DIFF_MULTI_F_FX_NCOB: - sample_len = sizeof(struct F_FX_NCOB); - break; - case MODE_MODEL_ZERO_F_FX_EFX_NCOB_ECOB: - case MODE_MODEL_MULTI_F_FX_EFX_NCOB_ECOB: - case MODE_DIFF_ZERO_F_FX_EFX_NCOB_ECOB: - case MODE_DIFF_MULTI_F_FX_EFX_NCOB_ECOB: - sample_len = sizeof(struct F_FX_EFX_NCOB_ECOB); + case CMP_MODE_STUFF: + if (cmp_par > MAX_STUFF_CMP_PAR) { + debug_print("Error: The selected %s stuff mode compression parameter: %u is too large, the largest possible value in the selected compression mode is: %u.\n", + par_name, cmp_par, MAX_STUFF_CMP_PAR); + cfg_invalid++; + } break; - case MODE_RAW_32: - case MODE_MODEL_ZERO_32: - case MODE_MODEL_MULTI_32: - case MODE_DIFF_ZERO_32: - case MODE_DIFF_MULTI_32: - sample_len = sizeof(uint32_t); + case CMP_MODE_DIFF_ZERO: + case CMP_MODE_DIFF_MULTI: + case CMP_MODE_MODEL_ZERO: + case CMP_MODE_MODEL_MULTI: + if (cmp_par < MIN_ICU_GOLOMB_PAR || + cmp_par > MAX_ICU_GOLOMB_PAR) { + debug_print("Error: The selected %s compression parameter: %u is not supported. The compression parameter has to be between [%u, %u].\n", + par_name, cmp_par, MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); + cfg_invalid++; + } + if (spill < MIN_ICU_SPILL) { + debug_print("Error: The selected %s spillover threshold value: %u is too small. Smallest possible spillover value is: %u.\n", + par_name, spill, MIN_ICU_SPILL); + cfg_invalid++; + } + if (spill > get_max_spill(cmp_par, data_type)) { + debug_print("Error: The selected %s spillover threshold value: %u is too large for the selected %s compression parameter: %u, the largest possible spillover value in the selected compression mode is: %u.\n", + par_name, spill, par_name, cmp_par, get_max_spill(cmp_par, data_type)); + cfg_invalid++; + } + break; default: - debug_print("Error: Compression mode not supported.\n"); - return 0; + debug_print("Error: The compression mode is not supported.\n"); + cfg_invalid++; break; } - return sample_len; + + if (cfg_invalid) + return 0; + + return 1; } /** - * @brief calculates the number of samples for a given data size for the - * different compression modes + * @brief check if the imagette specific compression parameters are valid * - * @param size size of the data me - * @param cmp_mode compression mode + * @param cfg pointer to the compressor configuration * - * @returns the number samples for the given compression mode; negative on error + * @returns 1 if the imagette specific parameters are valid, otherwise 0 */ -int cmp_input_size_to_samples(unsigned int size, unsigned int cmp_mode) +int cmp_cfg_imagette_is_valid(const struct cmp_cfg *cfg) { - unsigned int samples_size = size_of_a_sample(cmp_mode); + int cfg_invalid = 0; - if (!samples_size) - return -1; + if (!cfg) + return 0; - if (!rdcu_supported_mode_is_used(cmp_mode)) { - if (size < N_DPU_ICU_MULTI_ENTRY_HDR_SIZE) - return -1; - size -= N_DPU_ICU_MULTI_ENTRY_HDR_SIZE; + if (!cmp_imagette_data_type_is_used(cfg->data_type)) { + debug_print("Error: The compression data type is not an imagette compression data type.!\n"); + cfg_invalid++; } - if (size % samples_size) - return -1; + if (!cmp_pars_are_valid(cfg->golomb_par, cfg->spill, cfg->cmp_mode, + cfg->data_type, "imagette")) + cfg_invalid++; + + if (cmp_ap_imagette_data_type_is_used(cfg->data_type)) { + if (!cmp_pars_are_valid(cfg->ap1_golomb_par, cfg->ap1_spill, + cfg->cmp_mode, cfg->data_type, "adaptive 1 imagette")) + cfg_invalid++; + if (!cmp_pars_are_valid(cfg->ap2_golomb_par, cfg->ap2_spill, + cfg->cmp_mode, cfg->data_type, "adaptive 2 imagette")) + cfg_invalid++; + } - return size/samples_size; + if (cfg_invalid) + return 0; + + return 1; } /** - * @brief calculate the need bytes to hold a bitstream + * @brief check if the flux/center of brightness specific compression parameters are valid * - * @param cmp_size_bit compressed data size, measured in bits + * @param cfg pointer to the compressor configuration * - * @returns the size in bytes to store the hole bitstream - * @note we round up the result to multiples of 4 bytes + * @returns 1 if the flux/center of brightness specific parameters are valid, otherwise 0 */ -unsigned int cmp_bit_to_4byte(unsigned int cmp_size_bit) +int cmp_cfg_fx_cob_is_valid(const struct cmp_cfg *cfg) { - return (((cmp_size_bit + 7) / 8) + 3) & ~0x3UL; + int cfg_invalid = 0; + int check_exp_flags = 0, check_ncob = 0, check_efx = 0, check_ecob = 0, check_var = 0; + + if (!cfg) + return 0; + + if (!cmp_fx_cob_data_type_is_used(cfg->data_type)) { + debug_print("Error: The compression data type is not a flux/center of brightness compression data type.!\n"); + cfg_invalid++; + } + /* flux parameter is needed for every fx_cob data_type */ + if (!cmp_pars_are_valid(cfg->cmp_par_fx, cfg->spill_fx, cfg->cmp_mode, cfg->data_type, "flux")) + cfg_invalid++; + + switch (cfg->data_type) { + case DATA_TYPE_S_FX: + check_exp_flags = 1; + break; + case DATA_TYPE_S_FX_DFX: + check_exp_flags = 1; + check_efx = 1; + break; + case DATA_TYPE_S_FX_NCOB: + check_exp_flags = 1; + check_ncob = 1; + break; + case DATA_TYPE_S_FX_DFX_NCOB_ECOB: + check_exp_flags = 1; + check_ncob = 1; + check_efx = 1; + check_ecob = 1; + break; + case DATA_TYPE_L_FX: + check_exp_flags = 1; + check_var = 1; + break; + case DATA_TYPE_L_FX_DFX: + check_exp_flags = 1; + check_efx = 1; + check_var = 1; + break; + case DATA_TYPE_L_FX_NCOB: + check_exp_flags = 1; + check_ncob = 1; + check_var = 1; + break; + case DATA_TYPE_L_FX_DFX_NCOB_ECOB: + check_exp_flags = 1; + check_ncob = 1; + check_efx = 1; + check_ecob = 1; + check_var = 1; + break; + case DATA_TYPE_F_FX: + break; + case DATA_TYPE_F_FX_DFX: + check_efx = 1; + break; + case DATA_TYPE_F_FX_NCOB: + check_ncob = 1; + break; + case DATA_TYPE_F_FX_DFX_NCOB_ECOB: + check_ncob = 1; + check_efx = 1; + check_ecob = 1; + break; + default: + cfg_invalid++; + break; + } + + if (check_exp_flags && !cmp_pars_are_valid(cfg->cmp_par_exp_flags, cfg->spill_exp_flags, cfg->cmp_mode, cfg->data_type, "exposure flags")) + cfg_invalid++; + if (check_ncob && !cmp_pars_are_valid(cfg->cmp_par_ncob, cfg->spill_ncob, cfg->cmp_mode, cfg->data_type, "center of brightness")) + cfg_invalid++; + if (check_efx && !cmp_pars_are_valid(cfg->cmp_par_efx, cfg->spill_efx, cfg->cmp_mode, cfg->data_type, "extended flux")) + cfg_invalid++; + if (check_ecob && !cmp_pars_are_valid(cfg->cmp_par_ecob, cfg->spill_ecob, cfg->cmp_mode, cfg->data_type, "extended center of brightness")) + cfg_invalid++; + if (check_var && !cmp_pars_are_valid(cfg->cmp_par_fx_cob_variance, cfg->spill_fx_cob_variance, cfg->cmp_mode, cfg->data_type, "flux COB varianc")) + cfg_invalid++; + + if (cfg_invalid) + return 0; + + return 1; } /** - * @brief calculate the need bytes for the data + * @brief check if the auxiliary science specific compression parameters are valid * - * @param samples number of data samples - * @param cmp_mode used compression mode + * @param cfg pointer to the compressor configuration * - * @note for non RDCU modes the N_DPU ICU multi entry header size is added + * @returns 1 if the auxiliary science specific parameters are valid, otherwise 0 + */ + +int cmp_cfg_aux_is_valid(const struct cmp_cfg *cfg) +{ + int cfg_invalid = 0; + + if (!cfg) + return 0; + + if (!cmp_aux_data_type_is_used(cfg->data_type)) { + debug_print("Error: The compression data type is not an auxiliary science compression data type.!\n"); + cfg_invalid++; + } + + if (!cmp_pars_are_valid(cfg->cmp_par_mean, cfg->spill_mean, cfg->cmp_mode, cfg->data_type, "mean")) + cfg_invalid++; + if (!cmp_pars_are_valid(cfg->cmp_par_variance, cfg->spill_variance, cfg->cmp_mode, cfg->data_type, "variance")) + cfg_invalid++; + if (cfg->data_type != DATA_TYPE_OFFSET && cfg->data_type != DATA_TYPE_F_CAM_OFFSET) + if (!cmp_pars_are_valid(cfg->cmp_par_pixels_error, cfg->spill_pixels_error, cfg->cmp_mode, cfg->data_type, "outlier pixls num")) + cfg_invalid++; + + if (cfg_invalid) + return 0; + + return 1; +} + + +/** + * @brief check if a compression configuration is valid * - * @returns the size in bytes to store the data sample + * @param cfg pointer to the compressor configuration + * + * @returns 1 if the compression configuration is valid, otherwise 0 */ -unsigned int cmp_cal_size_of_data(unsigned int samples, unsigned int cmp_mode) +int cmp_cfg_is_valid(const struct cmp_cfg *cfg) { - unsigned int s = samples * size_of_a_sample(cmp_mode); + int cfg_invalid = 0; + + if (!cfg) + return 0; + + if (!cmp_cfg_icu_gen_par_is_valid(cfg)) + cfg_invalid++; + + if (!cmp_cfg_icu_buffers_is_valid(cfg)) + cfg_invalid++; + + if (cmp_imagette_data_type_is_used(cfg->data_type)) { + if (!cmp_cfg_imagette_is_valid(cfg)) + cfg_invalid++; + } else if (cmp_fx_cob_data_type_is_used(cfg->data_type)) { + if (!cmp_cfg_fx_cob_is_valid(cfg)) + cfg_invalid++; + } else if (cmp_aux_data_type_is_used(cfg->data_type)) { + if (!cmp_cfg_aux_is_valid(cfg)) + cfg_invalid++; + } else { + cfg_invalid++; + } - if (!rdcu_supported_mode_is_used(cmp_mode)) - s += N_DPU_ICU_MULTI_ENTRY_HDR_SIZE; + if (cfg_invalid) + return 0; - return s; + return 1; } @@ -630,7 +860,6 @@ unsigned int cmp_cal_size_of_data(unsigned int samples, unsigned int cmp_mode) * * @param cfg compressor configuration contains all parameters required for * compression - * */ void print_cmp_cfg(const struct cmp_cfg *cfg) diff --git a/lib/decmp.c b/lib/decmp.c index 4953f5d..c22e5e4 100644 --- a/lib/decmp.c +++ b/lib/decmp.c @@ -4,823 +4,43 @@ #include <limits.h> #include <string.h> -#include "../include/cmp_support.h" -#include "../include/cmp_icu.h" -#include "../include/cmp_data_types.h" -#include "../include/byteorder.h" -#include "../include/cmp_debug.h" +#include "byteorder.h" +#include "cmp_debug.h" +#include "cmp_support.h" +#include "cmp_data_types.h" +#include "cmp_entity.h" +#define CMP_ERROR_SAMLL_BUF -2 -double get_compression_ratio(const struct cmp_info *info) -{ - unsigned long orign_len_bits = info->samples_used * size_of_a_sample(info->cmp_mode_used) * CHAR_BIT; - - return (double)orign_len_bits/(double)info->cmp_size; -} - - -void *malloc_decompressed_data(const struct cmp_info *info) -{ - size_t sample_len; - - if (!info) - return NULL; - - if (info->samples_used == 0) - return NULL; - - sample_len = size_of_a_sample(info->cmp_mode_used); - - return malloc(info->samples_used * sample_len); -} - - -/** - * @brief decompression data pre-processing in RAW mode - * - * @note in RAW mode the data are uncompressed no pre_processing needed - * - * @param cmp_mode_used used compression mode - * - * @returns 0 on success, error otherwise - */ - -static int de_raw_pre_process(uint8_t cmp_mode_used) -{ - if (!raw_mode_is_used(cmp_mode_used)) - return -1; - - return 0; -} - - -/** - * @brief model decompression pre-processing - * - * @note change the data_buf in-place - * - * @param data_buf pointer to the data to process - * @param model_buf pointer to the model of the data to process - * @param samples_used the size of the data and model buffer in 16 bit units - * @param model_value_used used model weighting parameter - * @param round_used used number of bits to round; if zero no rounding takes place - * - * @returns 0 on success, error otherwise - */ - -static int de_model_16(uint16_t *data_buf, uint16_t *model_buf, uint32_t - samples_used, uint8_t model_value_used, uint8_t - round_used) -{ - size_t i; - int err; - - if (!samples_used) - return 0; - - if (!data_buf) - return -1; - - if (!model_buf) - return -1; - - if (model_value_used > MAX_MODEL_VALUE) - return -1; - - for (i = 0; i < samples_used; i++) { - /* overflow is intended */ - data_buf[i] = (uint16_t)(data_buf[i] + round_fwd(model_buf[i], - round_used)); - } - - err = de_lossy_rounding_16(data_buf, samples_used, round_used); - if (err) - return -1; - - for (i = 0; i < samples_used; i++) { - model_buf[i] = (uint16_t)cal_up_model(data_buf[i], model_buf[i], - model_value_used); - } - return 0; -} - - -static int de_model_S_FX(struct S_FX *data_buf, struct S_FX *model_buf, uint32_t - samples_used, uint8_t model_value_used, uint8_t - round_used) -{ - size_t i; - int err; - - if (!samples_used) - return 0; - - if (!data_buf) - return -1; - - if (!model_buf) - return -1; - - if (model_value_used > MAX_MODEL_VALUE) - return -1; - - for (i = 0; i < samples_used; i++) { - /* overflow is intended */ - struct S_FX round_model = model_buf[i]; - - lossy_rounding_S_FX(&round_model, 1, round_used); - data_buf[i] = add_S_FX(data_buf[i], model_buf[i]); - } - - err = de_lossy_rounding_S_FX(data_buf, samples_used, round_used); - if (err) - return -1; - - for (i = 0; i < samples_used; i++) - model_buf[i] = cal_up_model_S_FX(data_buf[i], model_buf[i], - model_value_used); - - return 0; -} - - -/** - * @brief 1d-differencing decompression per-processing - * - * @param data_buf pointer to the data to process - * @param samples_used the size of the data and model buffer in 16 bit units - * @param round_used used number of bits to round; if zero no rounding takes place - * - * @returns 0 on success, error otherwise - */ - -static int de_diff_16(uint16_t *data_buf, uint32_t samples_used, uint8_t - round_used) -{ - size_t i; - int err; - - if (!samples_used) - return 0; - - if (!data_buf) - return -1; - - for (i = 1; i < samples_used; i++) { - /* overflow intended */ - data_buf[i] = data_buf[i] + data_buf[i-1]; - } - - err = de_lossy_rounding_16(data_buf, samples_used, round_used); - if (err) - return -1; - - return 0; -} - - -static int de_diff_32(uint32_t *data_buf, uint32_t samples_used, uint8_t - round_used) -{ - size_t i; - int err; - - if (!samples_used) - return 0; - - if (!data_buf) - return -1; - - for (i = 1; i < samples_used; i++) { - /* overflow intended */ - data_buf[i] = data_buf[i] + data_buf[i-1]; - } - - err = de_lossy_rounding_32(data_buf, samples_used, round_used); - if (err) - return -1; - - return 0; -} - - -static int de_diff_S_FX(struct S_FX *data_buf, uint32_t samples_used, uint8_t - round_used) -{ - size_t i; - int err; - - if (!samples_used) - return 0; - - if (!data_buf) - return -1; - - for (i = 1; i < samples_used; i++) { - /* overflow intended */ - data_buf[i] = add_S_FX(data_buf[i], data_buf[i-1]); - } - - err = de_lossy_rounding_S_FX(data_buf, samples_used, round_used); - if (err) - return -1; - - return 0; -} - - -static int de_diff_S_FX_EFX(struct S_FX_EFX *data_buf, uint32_t samples_used, - uint8_t round_used) -{ - size_t i; - int err; - - if (!samples_used) - return 0; - - if (!data_buf) - return -1; - - for (i = 1; i < samples_used; i++) { - /* overflow intended */ - data_buf[i] = add_S_FX_EFX(data_buf[i], data_buf[i-1]); - } - - err = de_lossy_rounding_S_FX_EFX(data_buf, samples_used, round_used); - if (err) - return -1; - - return 0; -} - - -static int de_diff_S_FX_NCOB(struct S_FX_NCOB *data_buf, uint32_t samples_used, - uint8_t round_used) -{ - size_t i; - int err; - - if (!samples_used) - return 0; - - if (!data_buf) - return -1; - - for (i = 1; i < samples_used; i++) { - /* overflow intended */ - data_buf[i] = add_S_FX_NCOB(data_buf[i], data_buf[i-1]); - } - - err = de_lossy_rounding_S_FX_NCOB(data_buf, samples_used, round_used); - if (err) - return -1; - - return 0; -} - - -static int de_diff_S_FX_EFX_NCOB_ECOB(struct S_FX_EFX_NCOB_ECOB *data_buf, - uint32_t samples_used, uint8_t round_used) -{ - size_t i; - int err; - - if (!samples_used) - return 0; - - if (!data_buf) - return -1; - - for (i = 1; i < samples_used; i++) { - /* overflow intended */ - data_buf[i] = add_S_FX_EFX_NCOB_ECOB(data_buf[i], data_buf[i-1]); - } - - err = de_lossy_rounding_S_FX_EFX_NCOB_ECOB(data_buf, samples_used, - round_used); - if (err) - return -1; - - return 0; -} - - -static int de_pre_process(void *decoded_data, void *de_model_buf, - const struct cmp_info *info) -{ - if (!decoded_data) - return -1; - - if (!info) - return -1; - - if (info->samples_used == 0) - return 0; - - switch (info->cmp_mode_used) { - case MODE_RAW: - case MODE_RAW_S_FX: - return de_raw_pre_process(info->cmp_mode_used); - break; - case MODE_MODEL_ZERO: - case MODE_MODEL_MULTI: - return de_model_16((uint16_t *)decoded_data, - (uint16_t *)de_model_buf, info->samples_used, - info->model_value_used, info->round_used); - break; - case MODE_DIFF_ZERO: - case MODE_DIFF_MULTI: - return de_diff_16((uint16_t *)decoded_data, info->samples_used, - info->round_used); - break; - case MODE_MODEL_ZERO_S_FX: - case MODE_MODEL_MULTI_S_FX: - return de_model_S_FX((struct S_FX *)decoded_data, - (struct S_FX *)de_model_buf, - info->samples_used, info->model_value_used, - info->round_used); - break; - case MODE_DIFF_ZERO_S_FX: - case MODE_DIFF_MULTI_S_FX: - return de_diff_S_FX((struct S_FX *)decoded_data, - info->samples_used, info->round_used); - break; - case MODE_DIFF_ZERO_S_FX_EFX: - case MODE_DIFF_MULTI_S_FX_EFX: - return de_diff_S_FX_EFX((struct S_FX_EFX *)decoded_data, - info->samples_used, info->round_used); - break; - case MODE_MODEL_ZERO_S_FX_EFX: - case MODE_MODEL_MULTI_S_FX_EFX: - return -1; - break; - case MODE_DIFF_ZERO_S_FX_NCOB: - case MODE_DIFF_MULTI_S_FX_NCOB: - return de_diff_S_FX_NCOB((struct S_FX_NCOB *)decoded_data, - info->samples_used, info->round_used); - break; - case MODE_MODEL_ZERO_S_FX_NCOB: - case MODE_MODEL_MULTI_S_FX_NCOB: - return -1; - break; - case MODE_DIFF_ZERO_S_FX_EFX_NCOB_ECOB: - case MODE_DIFF_MULTI_S_FX_EFX_NCOB_ECOB: - return de_diff_S_FX_EFX_NCOB_ECOB((struct S_FX_EFX_NCOB_ECOB *) - decoded_data, - info->samples_used, - info->round_used); - break; - case MODE_MODEL_ZERO_S_FX_EFX_NCOB_ECOB: - case MODE_MODEL_MULTI_S_FX_EFX_NCOB_ECOB: - return -1; - break; - case MODE_DIFF_ZERO_F_FX: - case MODE_DIFF_MULTI_F_FX: - return de_diff_32((uint32_t *)decoded_data, info->samples_used, - info->round_used); - break; - case MODE_MODEL_ZERO_F_FX: - case MODE_MODEL_MULTI_F_FX: - return -1; - break; - default: - debug_print("Error: Compression mode not supported.\n"); - break; - } - - return -1; -} - - -static uint8_t de_map_to_pos_alg_8(uint8_t value_to_unmap) -{ - if (value_to_unmap & 0x1) /* if uneven */ - return (value_to_unmap + 1) / -2; - else - return value_to_unmap / 2; -} - - -static uint16_t de_map_to_pos_alg_16(uint16_t value_to_unmap) -{ - if (value_to_unmap & 0x1) /* if uneven */ - return (value_to_unmap + 1) / -2; - else - return value_to_unmap / 2; -} - - -static uint32_t de_map_to_pos_alg_32(uint32_t value_to_unmap) -{ - - if (value_to_unmap & 0x1) /* if uneven */ - return ((int64_t)value_to_unmap + 1) / -2; /* typecast to prevent overflow */ - else - return value_to_unmap / 2; -} - - -/** - * @brief map the unsigned output of the pre-stage to a signed value range for a - * 16-bit buffer - * - * @note change the data_buf in-place - * - * @param data_buf pointer to the uint16_t data buffer to process - * @param samples_used amount of data samples in the data_buf - * @param zero_mode_used needs to be set if the zero escape symbol mechanism is used - * - * @returns 0 on success, error otherwise - */ - -static int de_map_to_pos_16(uint16_t *data_buf, uint32_t samples_used, int - zero_mode_used) -{ - size_t i; - - if (!samples_used) - return 0; - - if (!data_buf) - return -1; - - for (i = 0; i < samples_used; i++) { - if (zero_mode_used) - data_buf[i] -= 1; - - data_buf[i] = (uint16_t)de_map_to_pos_alg_16(data_buf[i]); - } - return 0; -} - - -/** - * @brief map the unsigned output of the pre-stage to a signed value range for a - * 32-bit buffer - * - * @note change the data_buf in-place - * - * @param data_buf pointer to the uint16_t data buffer to process - * @param samples_used amount of data samples in the data_buf - * @param zero_mode_used needs to be set if the zero escape symbol mechanism is used - * - * @returns 0 on success, error otherwise - */ - -static int de_map_to_pos_32(uint32_t *data_buf, uint32_t samples_used, int - zero_mode_used) -{ - size_t i; - - if (!samples_used) - return 0; - - if (!data_buf) - return -1; - - for (i = 0; i < samples_used; i++) { - if (zero_mode_used) - data_buf[i] -= 1; - - data_buf[i] = (uint32_t)de_map_to_pos_alg_32(data_buf[i]); - } - return 0; -} - - -/** - * @brief map the unsigned output of the pre-stage to a signed value range for a - * S_FX buffer - * - * @note change the data_buf in-place - * - * @param data_buf pointer to the S_FX data buffer to process - * @param samples_used amount of data samples in the data_buf - * @param zero_mode_used needs to be set if the zero escape symbol mechanism is used - * - * @returns 0 on success, error otherwise - */ - -static int de_map_to_pos_S_FX(struct S_FX *data_buf, uint32_t samples_used, int - zero_mode_used) -{ - size_t i; - - if (!samples_used) - return 0; - - if (!data_buf) - return -1; - - for (i = 0; i < samples_used; i++) { - if (zero_mode_used) { - /* data_buf[i].EXPOSURE_FLAGS -= 1; */ - data_buf[i].FX -= 1; - } - - data_buf[i].EXPOSURE_FLAGS = - de_map_to_pos_alg_8(data_buf[i].EXPOSURE_FLAGS); - data_buf[i].FX = de_map_to_pos_alg_32(data_buf[i].FX); - } - return 0; -} - - -/** - * @brief map the unsigned output of the pre-stage to a signed value range for a - * S_FX_EFX buffer - * - * @note change the data_buf in-place - * - * @param data_buf pointer to the S_FX_EFX data buffer to process - * @param samples_used amount of data samples in the data_buf - * @param zero_mode_used needs to be set if the zero escape symbol mechanism is used - * - * @returns 0 on success, error otherwise - */ - -static int de_map_to_pos_S_FX_EFX(struct S_FX_EFX *data_buf, uint32_t - samples_used, int zero_mode_used) -{ - size_t i; - - if (!samples_used) - return 0; - - if (!data_buf) - return -1; - - for (i = 0; i < samples_used; i++) { - if (zero_mode_used) { - /* data_buf[i].EXPOSURE_FLAGS -= 1; */ - data_buf[i].FX -= 1; - data_buf[i].EFX -= 1; - } - - data_buf[i].EXPOSURE_FLAGS = - de_map_to_pos_alg_8(data_buf[i].EXPOSURE_FLAGS); - data_buf[i].FX = de_map_to_pos_alg_32(data_buf[i].FX); - data_buf[i].EFX = de_map_to_pos_alg_32(data_buf[i].EFX); - } - return 0; -} - - -/** - * @brief map the unsigned output of the pre-stage to a signed value range for a - * S_FX_NCOB buffer - * - * @note change the data_buf in-place - * - * @param data_buf pointer to the S_FX_NCOB data buffer to process - * @param samples_used amount of data samples in the data_buf - * @param zero_mode_used needs to be set if the zero escape symbol mechanism is used - * - * @returns 0 on success, error otherwise - */ - -static int de_map_to_pos_S_FX_NCOB(struct S_FX_NCOB *data_buf, uint32_t - samples_used, int zero_mode_used) -{ - size_t i; - - if (!samples_used) - return 0; - - if (!data_buf) - return -1; - - for (i = 0; i < samples_used; i++) { - if (zero_mode_used) { - /* data_buf[i].EXPOSURE_FLAGS -= 1; */ - data_buf[i].FX -= 1; - data_buf[i].NCOB_X -= 1; - data_buf[i].NCOB_Y -= 1; - } - - data_buf[i].EXPOSURE_FLAGS = - de_map_to_pos_alg_8(data_buf[i].EXPOSURE_FLAGS); - data_buf[i].FX = de_map_to_pos_alg_32(data_buf[i].FX); - data_buf[i].NCOB_X = de_map_to_pos_alg_32(data_buf[i].NCOB_X); - data_buf[i].NCOB_Y = de_map_to_pos_alg_32(data_buf[i].NCOB_Y); - } - return 0; -} - - -/** - * @brief map the unsigned output of the pre-stage to a signed value range for a - * S_FX_EFX_NCOB_ECOB buffer - * - * @note change the data_buf in-place - * - * @param data_buf pointer to the S_FX_EFX_NCOB_ECOB data buffer to process - * @param samples_used amount of data samples in the data_buf - * @param zero_mode_used needs to be set if the zero escape symbol mechanism is used - * - * @returns 0 on success, error otherwise - */ - -static int de_map_to_pos_S_FX_EFX_NCOB_ECOB(struct S_FX_EFX_NCOB_ECOB *data_buf, - uint32_t samples_used, - int zero_mode_used) -{ - size_t i; - - if (!samples_used) - return 0; - - if (!data_buf) - return -1; - - for (i = 0; i < samples_used; i++) { - if (zero_mode_used) { - /* data_buf[i].EXPOSURE_FLAGS -= 1; */ - data_buf[i].FX -= 1; - data_buf[i].NCOB_X -= 1; - data_buf[i].NCOB_Y -= 1; - data_buf[i].EFX -= 1; - data_buf[i].ECOB_X -= 1; - data_buf[i].ECOB_Y -= 1; - } - - data_buf[i].EXPOSURE_FLAGS = - de_map_to_pos_alg_8(data_buf[i].EXPOSURE_FLAGS); - data_buf[i].FX = de_map_to_pos_alg_32(data_buf[i].FX); - data_buf[i].NCOB_X = de_map_to_pos_alg_32(data_buf[i].NCOB_X); - data_buf[i].NCOB_Y = de_map_to_pos_alg_32(data_buf[i].NCOB_Y); - data_buf[i].EFX = de_map_to_pos_alg_32(data_buf[i].EFX); - data_buf[i].ECOB_X = de_map_to_pos_alg_32(data_buf[i].ECOB_X); - data_buf[i].ECOB_Y = de_map_to_pos_alg_32(data_buf[i].ECOB_Y); - } - return 0; -} - - -/** - * @brief map the unsigned output of the pre-stage to a signed value range for a - * F_FX buffer - * - * @note change the data_buf in-place - * - * @param data_buf pointer to the F_FX data buffer to process - * @param samples_used amount of data samples in the data_buf - * @param zero_mode_used needs to be set if the zero escape symbol mechanism is used - * - * @returns 0 on success, error otherwise - */ - -static int de_map_to_pos_F_FX(uint32_t *data_buf, uint32_t samples_used, int - zero_mode_used) -{ - return de_map_to_pos_32(data_buf, samples_used, zero_mode_used); -} +/* structure to hold a setup to encode a value */ +typedef unsigned int (*decoder_ptr)(unsigned int, unsigned int, unsigned int, unsigned int *); +struct decoder_setup { + /* generate_cw_f_pt generate_cw_f; /1* pointer to the code word generation function *1/ */ + decoder_ptr decode_cw_f; + int (*encode_method_f)(uint32_t *decoded_value, int stream_pos, + const struct decoder_setup *setup); /* pointer to the decoding function */ + uint32_t *bitstream_adr; /* start address of the compressed data bitstream */ + uint32_t max_stream_len; /* maximum length of the bitstream/icu_output_buf in bits */ + uint32_t max_cw_len; + uint32_t encoder_par1; /* encoding parameter 1 */ + uint32_t encoder_par2; /* encoding parameter 2 */ + uint32_t outlier_par; /* outlier parameter */ + uint32_t lossy_par; /* lossy compression parameter */ + uint32_t model_value; /* model value parameter */ + uint32_t max_data_bits; /* how many bits are needed to represent the highest possible value */ +}; -/** - * @brief map the unsigned output of the pre-stage to a signed value range - * - * @note change the data_buf in-place - * - * @param decompressed_data pointer to the data to process - * @param info compressor information contains information of - * an executed compression - * - * @returns 0 on success, error otherwise - */ -static int de_map_to_pos(void *decompressed_data, const struct cmp_info *info) +double get_compression_ratio(uint32_t samples, uint32_t cmp_size_bits, + enum cmp_data_type data_type) { - int zero_mode_used; - - if (!info) - return -1; - - if (info->samples_used == 0) - return 0; - - if (!decompressed_data) - return -1; + double orign_len_bits = (double)cmp_cal_size_of_data(samples, data_type) * CHAR_BIT; - zero_mode_used = zero_escape_mech_is_used(info->cmp_mode_used); - - switch (info->cmp_mode_used) { - case MODE_RAW: - case MODE_RAW_S_FX: - return 0; /* in raw mode no mapping is necessary */ - break; - case MODE_MODEL_ZERO: - case MODE_MODEL_MULTI: - case MODE_DIFF_ZERO: - case MODE_DIFF_MULTI: - return de_map_to_pos_16((uint16_t *)decompressed_data, - info->samples_used, zero_mode_used); - break; - case MODE_MODEL_ZERO_S_FX: - case MODE_MODEL_MULTI_S_FX: - case MODE_DIFF_ZERO_S_FX: - case MODE_DIFF_MULTI_S_FX: - return de_map_to_pos_S_FX((struct S_FX *)decompressed_data, - info->samples_used, zero_mode_used); - break; - case MODE_MODEL_ZERO_S_FX_EFX: - case MODE_MODEL_MULTI_S_FX_EFX: - case MODE_DIFF_ZERO_S_FX_EFX: - case MODE_DIFF_MULTI_S_FX_EFX: - return de_map_to_pos_S_FX_EFX((struct S_FX_EFX *) - decompressed_data, - info->samples_used, - zero_mode_used); - break; - case MODE_MODEL_ZERO_S_FX_NCOB: - case MODE_MODEL_MULTI_S_FX_NCOB: - case MODE_DIFF_ZERO_S_FX_NCOB: - case MODE_DIFF_MULTI_S_FX_NCOB: - return de_map_to_pos_S_FX_NCOB((struct S_FX_NCOB *) - decompressed_data, - info->samples_used, - zero_mode_used); - break; - case MODE_MODEL_ZERO_S_FX_EFX_NCOB_ECOB: - case MODE_MODEL_MULTI_S_FX_EFX_NCOB_ECOB: - case MODE_DIFF_ZERO_S_FX_EFX_NCOB_ECOB: - case MODE_DIFF_MULTI_S_FX_EFX_NCOB_ECOB: - return de_map_to_pos_S_FX_EFX_NCOB_ECOB((struct S_FX_EFX_NCOB_ECOB *) - decompressed_data, - info->samples_used, - zero_mode_used); - break; - case MODE_MODEL_ZERO_F_FX: - case MODE_MODEL_MULTI_F_FX: - case MODE_DIFF_ZERO_F_FX: - case MODE_DIFF_MULTI_F_FX: - return de_map_to_pos_F_FX((uint32_t *)decompressed_data, - info->samples_used, zero_mode_used); - break; - default: - debug_print("Error: Compression mode not supported.\n"); - break; - } - return -1; + return orign_len_bits/(double)cmp_size_bits; } -static unsigned int get_n_bits32(uint32_t *p_value, unsigned int bitOffset, - unsigned int nBits, const unsigned int *srcAddr, - size_t src_len_bit) -{ - const unsigned int *localAddr; - unsigned int bitsLeft, bitsRight, localEndPos; - unsigned int mask; - /*leave in case of erroneous input */ - if (nBits == 0) - return 0; - if (nBits > 32) - return 0; - if (!srcAddr) - return 0; - if (!p_value) - return 0; - if ((bitOffset + nBits) > src_len_bit) { - debug_print("Error: Buffer overflow detected.\n"); - return 0; - } - /* separate the bitOffset into word offset (set localAddr pointer) and - * local bit offset (bitsLeft) - */ - - localAddr = srcAddr + (bitOffset >> 5); - bitsLeft = bitOffset & 0x1f; - - localEndPos = bitsLeft + nBits; - - if (localEndPos <= 32) { - unsigned int shiftRight = 32 - nBits; - - bitsRight = shiftRight - bitsLeft; - - *(p_value) = *(localAddr) >> bitsRight; - - mask = (0xffffffff >> shiftRight); - - *(p_value) &= mask; - } else { - unsigned int n1 = 32 - bitsLeft; - unsigned int n2 = nBits - n1; - /* part 1 ; */ - mask = 0xffffffff >> bitsLeft; - *(p_value) = (*localAddr) & mask; - *(p_value) <<= n2; - /*part 2: */ - /* adjust address*/ - localAddr += 1; - - bitsRight = 32 - n2; - *(p_value) |= *(localAddr) >> bitsRight; - } - return nBits; -} - static unsigned int count_leading_ones(unsigned int value) { unsigned int n_ones = 0; /* number of leading 1s */ @@ -831,16 +51,15 @@ static unsigned int count_leading_ones(unsigned int value) leading_bit = value & 0x80000000; if (!leading_bit) break; - else { - n_ones++; - value <<= 1; - } + + n_ones++; + value <<= 1; } return n_ones; } -static unsigned int Rice_decoder(uint32_t code_word, unsigned int m, +static unsigned int rice_decoder(uint32_t code_word, unsigned int m, unsigned int log2_m, unsigned int *decoded_cw) { unsigned int q; /* quotient code */ @@ -863,7 +82,7 @@ static unsigned int Rice_decoder(uint32_t code_word, unsigned int m, code_word = code_word << ql; /* shift quotient code out */ - /* Right shifting an integer by a number of bits equal orgreater than + /* Right shifting an integer by a number of bits equal or greater than * its size is undefined behavior */ if (rl == 0) @@ -877,7 +96,7 @@ static unsigned int Rice_decoder(uint32_t code_word, unsigned int m, } -static unsigned int Golomb_decoder(unsigned int code_word, unsigned int m, +static unsigned int golomb_decoder(unsigned int code_word, unsigned int m, unsigned int log2_m, unsigned int *decoded_cw) { @@ -915,608 +134,382 @@ static unsigned int Golomb_decoder(unsigned int code_word, unsigned int m, } -typedef unsigned int (*decoder_ptr)(unsigned int, unsigned int, unsigned int, unsigned int *); - static decoder_ptr select_decoder(unsigned int golomb_par) { if (!golomb_par) return NULL; if (is_a_pow_of_2(golomb_par)) - return &Rice_decoder; + return &rice_decoder; else - return &Golomb_decoder; + return &golomb_decoder; } -static int decode_raw(const void *compressed_data, const struct cmp_info - *info, void *const decompressed_data) -{ - if (!info) - return -1; - if (info->samples_used == 0) - return 0; - if (!compressed_data) - return -1; - if (!decompressed_data) - return -1; - - if (info->samples_used*size_of_a_sample(info->cmp_mode_used)*CHAR_BIT - != info->cmp_size) { - debug_print("Warning: The size of the decompressed bitstream does not match the size of the compressed bitstream. Check if the parameters used for decompression are the same as those used for compression.\n"); - return 1; - } - memcpy(decompressed_data, compressed_data, info->cmp_size/CHAR_BIT); - - return 0; -} +/** + * @brief read a value of up to 32 bits from a bitstream + * + * @param p_value pointer to the read value + * @param n_bits number of bits to read from the bitstream + * @param bit_offset bit index where the bits will be read, seen from + * the very beginning of the bitstream + * @param bitstream_adr this is the pointer to the beginning of the + * bitstream (can be NULL) + * @param max_stream_len maximum length of the bitstream in bits; is + * ignored if bitstream_adr is NULL + * + * @returns length in bits of the generated bitstream on success; returns + * negative in case of erroneous input; returns CMP_ERROR_SAMLL_BUF if + * the bitstream buffer is too small to read the value from the bitstream + */ -static int decode_raw_16(const void *compressed_data, const struct cmp_info - *info, uint16_t *const decompressed_data) +static int get_n_bits32(uint32_t *p_value, unsigned int n_bits, int bit_offset, + uint32_t *bitstream_adr, unsigned int max_stream_len) { - size_t i; - uint16_t *p = decompressed_data; - uint32_t read_pos = 0; - unsigned int read_bits; - uint32_t read_val; + const unsigned int *local_adr; + unsigned int bitsLeft, bitsRight, localEndPos; + unsigned int mask; + int stream_len = (int)(n_bits + (unsigned int)bit_offset); /* overflow results in a negative return value */ - if (!info) - return -1; - if (info->samples_used == 0) - return 0; - if (!compressed_data) - return -1; - if (!decompressed_data) + /*leave in case of erroneous input */ + if (bit_offset < 0) return -1; - for (i = 0; i < info->samples_used; ++i) { - read_bits = get_n_bits32(&read_val, read_pos, 16, - compressed_data, info->cmp_size); - if (!read_bits) - return -1; - read_pos += 16; - p[i] =read_val; - } - return 0; -} - - -static int decode_raw_S_FX(const void *compressed_data, const struct cmp_info - *info, struct S_FX *const decompressed_data) -{ - int err = decode_raw(compressed_data, info, decompressed_data); - if (err) - return err; + if (n_bits == 0) + return stream_len; -#if defined(LITTLE_ENDIAN) - { - size_t i; - for (i = 0; i < info->samples_used; i++) { - decompressed_data[i].FX = cpu_to_be32(decompressed_data[i].FX); - } - } -#endif - return 0; -} + if (n_bits > 32) + return -1; -static unsigned int decode_normal(const void *compressed_data, - const struct cmp_info *info, - unsigned int read_pos, - unsigned int max_cw_len, - uint32_t *const decoded_val) -{ - decoder_ptr decoder; - unsigned int n_read_bits; - uint32_t read_val; - unsigned int n_bits; - unsigned int read_bits; - unsigned int log2_g; + if (!bitstream_adr) + return stream_len; - if (!compressed_data) - return -1U; + if (!p_value) + return stream_len; - if (!info) - return -1U; + /* Check if bitstream buffer is large enough */ + if ((unsigned int)stream_len > max_stream_len) { + debug_print("Error: Buffer overflow detected.\n"); + return CMP_ERROR_SAMLL_BUF; - if (!decoded_val) - return -1U; + } - if (read_pos > info->cmp_size) - return -1U; + /* separate the bit_offset into word offset (set local_adr pointer) and + * local bit offset (bitsLeft) + */ + local_adr = bitstream_adr + (bit_offset >> 5); + bitsLeft = bit_offset & 0x1f; - if (max_cw_len > 32) - return -1U; + localEndPos = bitsLeft + n_bits; - if (max_cw_len == 0) - return read_pos; + if (localEndPos <= 32) { + unsigned int shiftRight = 32 - n_bits; - decoder = select_decoder(info->golomb_par_used); - if (!decoder) - return -1U; + bitsRight = shiftRight - bitsLeft; - if (read_pos + max_cw_len > info->cmp_size) /* check buffer overflow */ - n_read_bits = info->cmp_size - read_pos; - else - n_read_bits = max_cw_len; + *(p_value) = cpu_to_be32(*(local_adr)) >> bitsRight; - read_bits = get_n_bits32(&read_val, read_pos, n_read_bits, - compressed_data, info->cmp_size); - if (!read_bits) - return -1U; + mask = (0xffffffff >> shiftRight); - read_val = read_val << (32 - n_read_bits); + *(p_value) &= mask; + } else { + unsigned int n1 = 32 - bitsLeft; + unsigned int n2 = n_bits - n1; + /* part 1 ; */ + mask = 0xffffffff >> bitsLeft; + *(p_value) = cpu_to_be32(*(local_adr)) & mask; + *(p_value) <<= n2; + /*part 2: */ + /* adjust address*/ + local_adr += 1; - log2_g = ilog_2(info->golomb_par_used); - n_bits = decoder(read_val, info->golomb_par_used, log2_g, decoded_val); + bitsRight = 32 - n2; + *(p_value) |= cpu_to_be32(*(local_adr)) >> bitsRight; + } - return read_pos + n_bits; + return stream_len; } -static unsigned int decode_zero(const void *compressed_data, - const struct cmp_info *info, - unsigned int read_pos, unsigned int max_cw_len, - uint32_t *const decoded_val) +static int decode_normal(uint32_t *decoded_value, int stream_pos, const struct decoder_setup *setup) { - if (!info) - return -1U; - - if (info->samples_used == 0) - return read_pos; - - if (!compressed_data) - return -1U; - - if (!decoded_val) - return -1U; - - if (read_pos > info->cmp_size) - return -1U; - - if (max_cw_len > 32) - return -1U; + uint32_t read_val = ~0U; + int n_read_bits, cw_len, n_bits; - if (max_cw_len == 0) - return read_pos; + if (stream_pos + setup->max_cw_len > setup->max_stream_len) /* check buffer overflow */ + n_read_bits = setup->max_stream_len - stream_pos; + else + n_read_bits = setup->max_cw_len; + if (n_read_bits >= 32 || n_read_bits == 0) + return -1; - read_pos = decode_normal(compressed_data, info, read_pos, max_cw_len, - decoded_val); - if (read_pos == -1U) - return -1U; - if (*decoded_val >= info->spill_used) /* consistency check */ - return -1U; + n_bits = get_n_bits32(&read_val, n_read_bits, stream_pos, + setup->bitstream_adr, setup->max_stream_len); + if (n_bits <= 0) + return -1; - if (*decoded_val == 0) {/* escape symbol mechanism was used; read unencoded value */ - unsigned int n_bits; - uint32_t unencoded_val; + read_val = read_val << (32 - n_read_bits); - n_bits = get_n_bits32(&unencoded_val, read_pos, max_cw_len, - compressed_data, info->cmp_size); - if (!n_bits) - return -1U; - if (unencoded_val < info->spill_used && unencoded_val != 0) /* consistency check */ - return -1U; + cw_len = setup->decode_cw_f(read_val, setup->encoder_par1, setup->encoder_par2, decoded_value); + if (cw_len < 0) + return -1; - *decoded_val = unencoded_val; - read_pos += n_bits; - } - return read_pos; + return stream_pos + cw_len; } -static unsigned int decode_multi(const void *compressed_data, - const struct cmp_info *info, - unsigned int read_pos, unsigned int max_cw_len, - uint32_t *const decoded_val) +static int decode_multi(uint32_t *decoded_value, int stream_pos, + const struct decoder_setup *setup) { - if (!info) - return -1U; - - if (info->samples_used == 0) - return read_pos; + stream_pos = decode_normal(decoded_value, stream_pos, setup); + if (stream_pos < 0) + return stream_pos; - if (!compressed_data) - return -1U; - - if (!decoded_val) - return -1U; - - if (read_pos > info->cmp_size) - return -1U; - - if (max_cw_len > 32) - return -1U; - - if (max_cw_len == 0) - return read_pos; - - read_pos = decode_normal(compressed_data, info, read_pos, max_cw_len, - decoded_val); - if (read_pos == -1U) - return -1U; - - if (*decoded_val >= info->spill_used) { + if (*decoded_value >= setup->outlier_par) { /* escape symbol mechanism was used; read unencoded value */ - unsigned int n_bits; - uint32_t unencoded_val; + int n_bits; + uint32_t unencoded_val = 0; unsigned int unencoded_len; - unencoded_len = (*decoded_val - info->spill_used + 1) * 2; - if (unencoded_len > max_cw_len) /* consistency check */ - return -1U; + unencoded_len = (*decoded_value - setup->outlier_par + 1) * 2; - /* check buffer overflow */ - if ((read_pos + unencoded_len) > info->cmp_size) { - /*TODO: debug message */ - return -1U; - } - n_bits = get_n_bits32(&unencoded_val, read_pos, unencoded_len, - compressed_data, info->cmp_size); - if (!n_bits) - return -1U; + n_bits = get_n_bits32(&unencoded_val, unencoded_len, stream_pos, + setup->bitstream_adr, setup->max_stream_len); + if (n_bits <= 0) + return -1; - *decoded_val = unencoded_val + info->spill_used; - read_pos += n_bits; + *decoded_value = unencoded_val + setup->outlier_par; + stream_pos += unencoded_len; } - return read_pos; -} - - -static unsigned int decode_value(const void *compressed_data, - const struct cmp_info *info, - unsigned int read_pos, - unsigned int max_cw_len, uint32_t *decoded_val) -{ - if (multi_escape_mech_is_used(info->cmp_mode_used)) - return decode_multi(compressed_data, info, read_pos, max_cw_len, - decoded_val); - - if (zero_escape_mech_is_used(info->cmp_mode_used)) - return decode_zero(compressed_data, info, read_pos, max_cw_len, - decoded_val); - return -1U; + return stream_pos; } -static int decode_16(const void *compressed_data, const struct cmp_info *info, - uint16_t *decoded_data) +static int decode_zero(uint32_t *decoded_value, int stream_pos, + const struct decoder_setup *setup) { - size_t i; - unsigned int read_pos = 0; - - if (!info) - return -1; + stream_pos = decode_normal(decoded_value, stream_pos, setup); - if (info->samples_used == 0) - return 0; + if (stream_pos <= 0) + return stream_pos; - if (!decoded_data) + if (*decoded_value > setup->outlier_par) /* consistency check */ return -1; - for (i = 0; i < info->samples_used; i++) { - uint32_t decoded_val; + if (*decoded_value == 0) {/* escape symbol mechanism was used; read unencoded value */ + int n_bits; + uint32_t unencoded_val = 0; - read_pos = decode_value(compressed_data, info, read_pos, 16, - &decoded_val); - if (read_pos == -1U) { - debug_print("Error: Compressed values could not be decoded.\n"); + n_bits = get_n_bits32(&unencoded_val, setup->max_data_bits, stream_pos, + setup->bitstream_adr, setup->max_stream_len); + if (n_bits <= 0) return -1; - } - - if (decoded_val > UINT16_MAX) + if (unencoded_val < setup->outlier_par && unencoded_val != 0) /* consistency check */ return -1; - decoded_data[i] = (uint16_t)decoded_val; + *decoded_value = unencoded_val; + stream_pos += setup->max_data_bits; } - - if (read_pos != info->cmp_size && - cmp_bit_to_4byte(read_pos) != cmp_bit_to_4byte(info->cmp_size)) { - debug_print("Warning: The size of the decompressed bitstream does not match the size of the compressed bitstream. Check if the parameters used for decompression are the same as those used for compression.\n"); - return 1; - } - return 0; + (*decoded_value)--; + if (*decoded_value == 0xFFFFFFFF) /* catch underflow */ + (*decoded_value) = (*decoded_value) >> (32-setup->max_data_bits); + return stream_pos; } -static int decode_S_FX(const void *compressed_data, const struct cmp_info *info, - struct S_FX *decoded_data) -{ - size_t i; - unsigned int read_pos = 0; - struct cmp_info info_exp_flag; +/** + * @brief remap a unsigned value back to a signed value + * @note this is the reverse function of map_to_pos() + * + * @param value_to_unmap unsigned value to remap + * + * @returns the signed remapped value + */ - info_exp_flag.golomb_par_used = GOLOMB_PAR_EXPOSURE_FLAGS; +static uint32_t re_map_to_pos(uint32_t value_to_unmap) +{ + if (value_to_unmap & 0x1) { /* if uneven */ + if (value_to_unmap == 0xFFFFFFFF) /* catch overflow */ + return 0x80000000; + return -((value_to_unmap + 1) / 2); + } else + return value_to_unmap / 2; +} - if (!info) - return -1; - if (info->samples_used == 0) - return 0; +static int decode_value(uint32_t *decoded_value, uint32_t model, + int stream_pos, const struct decoder_setup *setup) +{ + uint32_t mask = (~0U >> (32 - setup->max_data_bits)); /* mask the used bits */ - if (!decoded_data) - return -1; + stream_pos = setup->encode_method_f(decoded_value, stream_pos, setup); + if (stream_pos <= 0) + return stream_pos; - info_exp_flag = *info; + *decoded_value = re_map_to_pos(*decoded_value); //, setup->max_used_bits); - for (i = 0; i < info->samples_used; i++) { - uint32_t decoded_val; + *decoded_value += round_fwd(model, setup->lossy_par); - /* read_pos = decode_value(compressed_data, &info_exp_flag, read_pos, 8, */ - /* &decoded_val); */ - read_pos = decode_normal(compressed_data, &info_exp_flag, read_pos, 8, - &decoded_val); - if (read_pos == -1U) - return -1; - if (decoded_val > UINT8_MAX) - return -1; - decoded_data[i].EXPOSURE_FLAGS = (uint8_t)decoded_val; + *decoded_value &= mask; - read_pos = decode_value(compressed_data, info, read_pos, 32, - &decoded_val); - if (read_pos == -1U) - return -1; - decoded_data[i].FX = decoded_val; - } + *decoded_value = round_inv(*decoded_value, setup->lossy_par); - if (read_pos != info->cmp_size) { - debug_print("Warning: The size of the decompressed bitstream does not match the size of the compressed bitstream. Check if the parameters used for decompression are the same as those used for compression."); - return 1; - } - return 0; + return stream_pos; } -static int decode_S_FX_EFX(const void *compressed_data, const struct cmp_info - *info, struct S_FX_EFX *decoded_data) +static int configure_decoder_setup(struct decoder_setup *setup, + uint32_t cmp_par, uint32_t spill, + uint32_t lossy_par, uint32_t max_data_bits, + const struct cmp_cfg *cfg) { - size_t i; - unsigned int read_pos = 0; - - if (!info) + if (multi_escape_mech_is_used(cfg->cmp_mode)) + setup->encode_method_f = &decode_multi; + else if (zero_escape_mech_is_used(cfg->cmp_mode)) + setup->encode_method_f = &decode_zero; + else { + debug_print("Error: Compression mode not supported.\n"); return -1; + } - if (info->samples_used == 0) - return 0; - - if (!decoded_data) + setup->bitstream_adr = cfg->icu_output_buf; /* start address of the compressed data bitstream */ + if (cfg->buffer_length & 0x3) { + debug_print("Error: The length of the compressed data is not a multiple of 4 bytes."); return -1; - - for (i = 0; i < info->samples_used; i++) { - uint32_t decoded_val; - - read_pos = decode_value(compressed_data, info, read_pos, 8, - &decoded_val); - if (read_pos == -1U) - return -1; - if (decoded_val > UINT8_MAX) - return -1; - decoded_data[i].EXPOSURE_FLAGS = (uint8_t)decoded_val; - - read_pos = decode_value(compressed_data, info, read_pos, 32, - &decoded_val); - if (read_pos == -1U) - return -1; - decoded_data[i].FX = decoded_val; - - read_pos = decode_value(compressed_data, info, read_pos, 32, - &decoded_val); - if (read_pos == -1U) - return -1; - decoded_data[i].EFX = decoded_val; } + setup->max_stream_len = (cfg->buffer_length) * CHAR_BIT; /* maximum length of the bitstream/icu_output_buf in bits */ + if (rdcu_supported_data_type_is_used(cfg->data_type)) + setup->max_cw_len = 16; + else + setup->max_cw_len = 32; + setup->encoder_par1 = cmp_par; /* encoding parameter 1 */ + setup->encoder_par2 = ilog_2(cmp_par); /* encoding parameter 2 */ + setup->outlier_par = spill; /* outlier parameter */ + setup->lossy_par = lossy_par; /* lossy compression parameter */ + setup->model_value = cfg->model_value; /* model value parameter */ + setup->max_data_bits = max_data_bits; /* how many bits are needed to represent the highest possible value */ + setup->decode_cw_f = select_decoder(cmp_par); - if (read_pos != info->cmp_size) { - debug_print("Warning: The size of the decompressed bitstream does not match the size of the compressed bitstream. Check if the parameters used for decompression are the same as those used for compression.\n"); - return 1; - } return 0; } -static int decode_S_FX_NCOB(const void *compressed_data, const struct cmp_info - *info, struct S_FX_NCOB *decoded_data) +static int decompress_imagette(struct cmp_cfg *cfg) { size_t i; - unsigned int read_pos = 0; - - if (!info) - return -1; - - if (info->samples_used == 0) - return 0; + int stream_pos = 0; + struct decoder_setup setup; + uint16_t *decompressed_data = cfg->input_buf; + uint16_t *model_buf = cfg->model_buf; + uint16_t *up_model_buf = cfg->icu_new_model_buf; + uint32_t decoded_value = 0; + uint16_t model; + int err; - if (!decoded_data) + err = configure_decoder_setup(&setup, cfg->golomb_par, cfg->spill, + cfg->round, 16, cfg); + if (err) return -1; - for (i = 0; i < info->samples_used; i++) { - uint32_t decoded_val; - - read_pos = decode_value(compressed_data, info, read_pos, 8, - &decoded_val); - if (read_pos == -1U) - return -1; - if (decoded_val > UINT8_MAX) - return -1; - decoded_data[i].EXPOSURE_FLAGS = (uint8_t)decoded_val; - read_pos = decode_value(compressed_data, info, read_pos, 32, - &decoded_val); - if (read_pos == -1U) - return -1; - decoded_data[i].FX = decoded_val; + for (i = 0; i < cfg->samples; i++) { + if (model_mode_is_used(cfg->cmp_mode)) + model = model_buf[i]; + else + model = decoded_value; - read_pos = decode_value(compressed_data, info, read_pos, 32, - &decoded_val); - if (read_pos == -1U) - return -1; - decoded_data[i].NCOB_X = decoded_val; + stream_pos = decode_value(&decoded_value, model, stream_pos, &setup); + if (stream_pos <= 0) + return stream_pos; + decompressed_data[i] = decoded_value; - read_pos = decode_value(compressed_data, info, read_pos, 32, - &decoded_val); - if (read_pos == -1U) - return -1; - decoded_data[i].NCOB_Y = decoded_val; + if (up_model_buf) { + up_model_buf[i] = cmp_up_model(decoded_value, model, + cfg->model_value, setup.lossy_par); + } } - if (read_pos != info->cmp_size) { - debug_print("Warning: The size of the decompressed bitstream does not match the size of the compressed bitstream. Check if the parameters used for decompression are the same as those used for compression.\n"); - return 1; - } - return 0; + return stream_pos; } - -static int decode_S_FX_EFX_NCOB_ECOB(const void *compressed_data, - const struct cmp_info *info, - struct S_FX_EFX_NCOB_ECOB *decoded_data) +static int decompressed_data_internal(struct cmp_cfg *cfg) { - size_t i; - unsigned int read_pos = 0; - - if (!info) - return -1; - - if (info->samples_used == 0) - return 0; + int data_size, strem_len_bit = -1; - if (!decoded_data) + if (!cfg) + return 0; /* or -1? */ + if (!cfg->icu_output_buf) return -1; - for (i = 0; i < info->samples_used; i++) { - uint32_t decoded_val; - - read_pos = decode_value(compressed_data, info, read_pos, 8, - &decoded_val); - if (read_pos == -1U) - return -1; - if (decoded_val > UINT8_MAX) - return -1; - decoded_data[i].EXPOSURE_FLAGS = (uint8_t)decoded_val; + data_size = cmp_cal_size_of_data(cfg->samples, cfg->data_type); + if (!cfg->input_buf || !data_size) + return data_size; - read_pos = decode_value(compressed_data, info, read_pos, 32, - &decoded_val); - if (read_pos == -1U) + if (model_mode_is_used(cfg->cmp_mode)) + if (!cfg->model_buf) return -1; - decoded_data[i].FX = decoded_val; - read_pos = decode_value(compressed_data, info, read_pos, 32, - &decoded_val); - if (read_pos == -1U) - return -1; - decoded_data[i].NCOB_X = decoded_val; + if (cfg->cmp_mode == CMP_MODE_RAW) { - read_pos = decode_value(compressed_data, info, read_pos, 32, - &decoded_val); - if (read_pos == -1U) + if ((unsigned int)data_size < cfg->buffer_length/CHAR_BIT) return -1; - decoded_data[i].NCOB_Y = decoded_val; - read_pos = decode_value(compressed_data, info, read_pos, 32, - &decoded_val); - if (read_pos == -1U) - return -1; - decoded_data[i].EFX = decoded_val; + if (cfg->input_buf) { + memcpy(cfg->input_buf, cfg->icu_output_buf, data_size); + if (cmp_input_big_to_cpu_endianness(cfg->input_buf, data_size, cfg->data_type)) + return -1; + strem_len_bit = data_size * CHAR_BIT; + } - read_pos = decode_value(compressed_data, info, read_pos, 32, - &decoded_val); - if (read_pos == -1U) - return -1; - decoded_data[i].ECOB_X = decoded_val; + } else { + switch (cfg->data_type) { + case DATA_TYPE_IMAGETTE: + strem_len_bit = decompress_imagette(cfg); + break; + default: + strem_len_bit = -1; + debug_print("Error: Compressed data type not supported.\n"); + break; + } - read_pos = decode_value(compressed_data, info, read_pos, 32, - &decoded_val); - if (read_pos == -1U) - return -1; - decoded_data[i].ECOB_Y = decoded_val; } + /* TODO: is this usefull? if (strem_len_bit != data_size * CHAR_BIT) { */ + if (strem_len_bit <= 0) + return -1; - if (read_pos != info->cmp_size) { - debug_print("Warning: The size of the decompressed bitstream does not match the size of the compressed bitstream. Check if the parameters used for decompression are the same as those used for compression."); - return 1; - } - return 0; + + return data_size; } -static int decode_data(const void *compressed_data, const struct cmp_info *info, - void *decompressed_data) +int decompress_cmp_entiy(struct cmp_entity *ent, void *model_buf, + void *up_model_buf, void *decompressed_data) { - if (!info) - return -1; + int err; + struct cmp_cfg cfg = {0}; - if (info->samples_used == 0) - return 0; + cfg.model_buf = model_buf; + cfg.icu_new_model_buf = up_model_buf; + cfg.input_buf = decompressed_data; - if (!compressed_data) + if (!ent) return -1; - if (!decompressed_data) + err = cmp_ent_read_header(ent, &cfg); + if (err) return -1; - switch (info->cmp_mode_used) { - case MODE_RAW: - return decode_raw_16(compressed_data, info, decompressed_data); - break; - case MODE_MODEL_ZERO: - case MODE_DIFF_ZERO: - case MODE_MODEL_MULTI: - case MODE_DIFF_MULTI: - return decode_16(compressed_data, info, - (uint16_t *)decompressed_data); - break; - case MODE_RAW_S_FX: - return decode_raw_S_FX(compressed_data, info, decompressed_data); - break; - case MODE_MODEL_ZERO_S_FX: - case MODE_MODEL_MULTI_S_FX: - case MODE_DIFF_ZERO_S_FX: - case MODE_DIFF_MULTI_S_FX: - return decode_S_FX(compressed_data, info, - (struct S_FX *)decompressed_data); - break; - case MODE_MODEL_ZERO_S_FX_EFX: - case MODE_MODEL_MULTI_S_FX_EFX: - case MODE_DIFF_ZERO_S_FX_EFX: - case MODE_DIFF_MULTI_S_FX_EFX: - return decode_S_FX_EFX(compressed_data, info, - (struct S_FX_EFX *)decompressed_data); - break; - case MODE_MODEL_ZERO_S_FX_NCOB: - case MODE_MODEL_MULTI_S_FX_NCOB: - case MODE_DIFF_ZERO_S_FX_NCOB: - case MODE_DIFF_MULTI_S_FX_NCOB: - return decode_S_FX_NCOB(compressed_data, info, - (struct S_FX_NCOB *)decompressed_data); - break; - case MODE_MODEL_ZERO_S_FX_EFX_NCOB_ECOB: - case MODE_MODEL_MULTI_S_FX_EFX_NCOB_ECOB: - case MODE_DIFF_ZERO_S_FX_EFX_NCOB_ECOB: - case MODE_DIFF_MULTI_S_FX_EFX_NCOB_ECOB: - return decode_S_FX_EFX_NCOB_ECOB(compressed_data, info, - (struct S_FX_EFX_NCOB_ECOB *) - decompressed_data); - break; -#if 0 - case MODE_MODEL_ZERO_F_FX: - case MODE_MODEL_MULTI_F_FX: - case MODE_DIFF_ZERO_F_FX: - case MODE_DIFF_MULTI_F_FX: - break; -#endif - default: - debug_print("Error: Compression mode not supported.\n"); - break; - - } - return -1; + return decompressed_data_internal(&cfg); } -/* model buffer is overwritten with updated model*/ -int decompress_data(const void *compressed_data, void *de_model_buf, const - struct cmp_info *info, void *decompressed_data) +/* model buffer is overwritten with updated model */ + +int decompress_data(uint32_t *compressed_data, void *de_model_buf, + const struct cmp_info *info, void *decompressed_data) { - int err; + int size_decomp_data; + struct cmp_cfg cfg = {0}; if (!compressed_data) return -1; @@ -1535,18 +528,22 @@ int decompress_data(const void *compressed_data, void *de_model_buf, const if (!decompressed_data) return -1; + /* cfg.data_type = info->data_type_used; */ + cfg.cmp_mode = info->cmp_mode_used; + cfg.model_value = info->model_value_used; + cfg.round = info->round_used; + cfg.spill = info->spill_used; + cfg.golomb_par = info->golomb_par_used; + cfg.samples = info->samples_used; + cfg.icu_output_buf = compressed_data; + cfg.buffer_length = cmp_bit_to_4byte(info->cmp_size); + cfg.input_buf = decompressed_data; + cfg.model_buf = de_model_buf; + size_decomp_data = decompressed_data_internal(&cfg); + if (size_decomp_data <= 0) + return -1; + else + return 0; - err = decode_data(compressed_data, info, decompressed_data); - if (err) - return err; - - err = de_map_to_pos(decompressed_data, info); - if (err) - return err; - - err = de_pre_process(decompressed_data, de_model_buf, info); - if (err) - return err; - return 0; } diff --git a/lib/meson.build b/lib/meson.build index 21f72ad..f072ce4 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -17,6 +17,6 @@ cmplib_sources = files([ cmp_lib = static_library('cmp_lib', sources : cmplib_sources, include_directories : incdir, - c_args : ['-DDEBUGLEVEL=1'], - install : 'true' # linking under windows mingw only works if this is set + c_args : ['-DDEBUGLEVEL=1'], +# install : 'true' # linking under windows mingw only works if this is set ) diff --git a/lib/rdcu_pkt_to_file.c b/lib/rdcu_pkt_to_file.c index 83074d7..0c71c8e 100644 --- a/lib/rdcu_pkt_to_file.c +++ b/lib/rdcu_pkt_to_file.c @@ -28,11 +28,11 @@ #include <errno.h> #include <sys/stat.h> -#include "../include/rdcu_pkt_to_file.h" -#include "../include/cmp_rdcu_extended.h" -#include "../include/rdcu_rmap.h" -#include "../include/rdcu_ctrl.h" -#include "../include/rdcu_cmd.h" +#include "rdcu_pkt_to_file.h" +#include "cmp_rdcu_extended.h" +#include "rdcu_rmap.h" +#include "rdcu_ctrl.h" +#include "rdcu_cmd.h" /* Name of directory were the RMAP packages are stored */ static char tc_folder_dir[MAX_TC_FOLDER_DIR_LEN] = "TC_FILES"; @@ -52,7 +52,6 @@ void set_tc_folder_dir(const char *dir_name) strncpy(tc_folder_dir, dir_name, sizeof(tc_folder_dir)); /* Ensure null-termination. */ tc_folder_dir[sizeof(tc_folder_dir) - 1] = '\0'; - return; } @@ -292,8 +291,7 @@ static int read_rdcu_pkt_mode_cfg(uint8_t *icu_addr, uint8_t *rdcu_addr, if (read_all < 0x7) return -1; - printf("Use ICU_ADDR = %#02X, RDCU_ADDR = %#02X and MTU = %d for the " - "RAMP packets.\n", *icu_addr, *rdcu_addr, *mtu); + printf("Use ICU_ADDR = %#02X, RDCU_ADDR = %#02X and MTU = %d for the RAMP packets.\n", *icu_addr, *rdcu_addr, *mtu); return 0; } diff --git a/meson.build b/meson.build index d300c11..6757167 100644 --- a/meson.build +++ b/meson.build @@ -5,6 +5,8 @@ project('cmp_tool', 'c', default_options : ['warning_level=3', 'c_std=gnu99'] ) +add_project_arguments('-DDEBUGLEVEL=1', language : 'c') + if host_machine.system() == 'windows' and meson.get_compiler('c').get_id() == 'gcc' # by default, MinGW on win32 behaves as if it ignores __attribute__((packed)), # you need to add -mno-ms-bitfields to make it work as expected. diff --git a/test/cmp_icu/meson.build b/test/cmp_icu/meson.build index 80c3e27..405d376 100644 --- a/test/cmp_icu/meson.build +++ b/test/cmp_icu/meson.build @@ -1,14 +1,26 @@ -test_cmp_icu_files = files ([ - 'test_cmp_icu.c' -]) +test_case = files('test_cmp_icu.c') +test_runner = test_runner_generator.process(test_case) test_cmp_icu = executable('test_cmp_icu', - test_cmp_icu_files, + test_case, test_runner, include_directories : incdir, link_with : cmp_lib, - # c_args : ['-DMAIN'] - dependencies : cunit_dep, + dependencies : unity_dep, build_by_default : false ) -test('cmp_icu unit test', test_cmp_icu) +test('cmp_icu Unit Tests', test_cmp_icu) + + +test_case = files('test_decmp.c') +test_runner = test_runner_generator.process(test_case) + +test_cmp_decomp = executable('test_cmp_decomp', + test_case, test_runner, + include_directories : incdir, + link_with : cmp_lib, + dependencies : unity_dep, + build_by_default : false +) + +test('Compression Decompression Unit Tests', test_cmp_decomp) diff --git a/test/cmp_icu/test_cmp_icu.c b/test/cmp_icu/test_cmp_icu.c index 8bf30d6..35f938a 100644 --- a/test/cmp_icu/test_cmp_icu.c +++ b/test/cmp_icu/test_cmp_icu.c @@ -1,128 +1,1388 @@ -/* - * Simple example of a CUnit unit test. - * - * This program (crudely) demonstrates a very simple "black box" - * test of the standard library functions fprintf() and fread(). - * It uses suite initialization and cleanup functions to open - * and close a common temporary file used by the test functions. - * The test functions then write to and read from the temporary - * file in the course of testing the library functions. - * - * The 2 test functions are added to a single CUnit suite, and - * then run using the CUnit Basic interface. The output of the - * program (on CUnit version 2.0-2) is: - * - * CUnit : A Unit testing framework for C. - * http://cunit.sourceforge.net/ - * - * Suite: Suite_1 - * Test: test of fprintf() ... passed - * Test: test of fread() ... passed - * - * --Run Summary: Type Total Ran Passed Failed - * suites 1 1 n/a 0 - * tests 2 2 2 0 - * asserts 5 5 5 0 +#include <string.h> +#include <stdlib.h> + +#include "unity.h" + +#include "cmp_support.h" +/* this is a hack to test static functions */ +#include "../lib/cmp_icu.c" + + +/** + * @test map_to_pos */ -#include <stdio.h> -#include <string.h> -#include "CUnit/Basic.h" +void test_map_to_pos(void) +{ + uint32_t value_to_map; + uint32_t max_data_bits; + uint32_t mapped_value; + + /* test mapping 32 bits values */ + max_data_bits = 32; + + value_to_map = 0; + mapped_value = map_to_pos(value_to_map, max_data_bits); + TEST_ASSERT_EQUAL_INT(0, mapped_value); + + value_to_map = -1U; + mapped_value = map_to_pos(value_to_map, max_data_bits); + TEST_ASSERT_EQUAL_INT(1, mapped_value); + + value_to_map = 1; + mapped_value = map_to_pos(value_to_map, max_data_bits); + TEST_ASSERT_EQUAL_INT(2, mapped_value); + + value_to_map = 42; + mapped_value = map_to_pos(value_to_map, max_data_bits); + TEST_ASSERT_EQUAL_INT(84, mapped_value); + + value_to_map = INT32_MAX; + mapped_value = map_to_pos(value_to_map, max_data_bits); + TEST_ASSERT_EQUAL_HEX(UINT32_MAX-1, mapped_value); + + value_to_map = INT32_MIN; + mapped_value = map_to_pos(value_to_map, max_data_bits); + TEST_ASSERT_EQUAL_HEX(UINT32_MAX, mapped_value); + + /* test mapping 16 bits values */ + max_data_bits = 16; + + value_to_map = -1U; + mapped_value = map_to_pos(value_to_map, max_data_bits); + TEST_ASSERT_EQUAL_INT(1, mapped_value); + + /* test mapping 6 bits values */ + max_data_bits = 6; -/* Pointer to the file used by the tests. */ -static FILE* temp_file = NULL; + value_to_map = 0; + mapped_value = map_to_pos(value_to_map, max_data_bits); + TEST_ASSERT_EQUAL_INT(0, mapped_value); -/* The suite initialization function. - * Opens the temporary file used by the tests. - * Returns zero on success, non-zero otherwise. + value_to_map = -1U; + mapped_value = map_to_pos(value_to_map, max_data_bits); + TEST_ASSERT_EQUAL_INT(1, mapped_value); + + value_to_map = UINT32_MAX; + mapped_value = map_to_pos(value_to_map, max_data_bits); + TEST_ASSERT_EQUAL_INT(1, mapped_value); + + value_to_map = -1U & 0x3FU; + mapped_value = map_to_pos(value_to_map, max_data_bits); + TEST_ASSERT_EQUAL_INT(1, mapped_value); + + value_to_map = 63; + mapped_value = map_to_pos(value_to_map, max_data_bits); + TEST_ASSERT_EQUAL_INT(1, mapped_value); + + value_to_map = 1; + mapped_value = map_to_pos(value_to_map, max_data_bits); + TEST_ASSERT_EQUAL_INT(2, mapped_value); + + value_to_map = 31; + mapped_value = map_to_pos(value_to_map, max_data_bits); + TEST_ASSERT_EQUAL_INT(62, mapped_value); + + value_to_map = -33U; /* aka 31 */ + mapped_value = map_to_pos(value_to_map, max_data_bits); + TEST_ASSERT_EQUAL_INT(62, mapped_value); + + value_to_map = -32U; + mapped_value = map_to_pos(value_to_map, max_data_bits); + TEST_ASSERT_EQUAL_INT(63, mapped_value); + + value_to_map = 32; + mapped_value = map_to_pos(value_to_map, max_data_bits); + TEST_ASSERT_EQUAL_INT(63, mapped_value); +} + + +/** + * @test put_n_bits32 */ -int init_suite1(void) + +#define SDP_PB_N 3 + + +static void init_PB32_arrays(uint32_t *z, uint32_t *o) { - if (NULL == (temp_file = fopen("temp.txt", "w+"))) { - return -1; - } - else { - return 0; + uint32_t i; + + /* init testarray with all 0 and all 1 */ + for (i = 0; i < SDP_PB_N; i++) { + z[i] = 0; + o[i] = 0xffffffff; } } -/* The suite cleanup function. - * Closes the temporary file used by the tests. - * Returns zero on success, non-zero otherwise. + +void test_put_n_bits32(void) +{ + uint32_t v, n; + int o, rval; /* return value */ + uint32_t testarray0[SDP_PB_N]; + uint32_t testarray1[SDP_PB_N]; + const uint32_t l = sizeof(testarray0) * CHAR_BIT; + + /* hereafter, the value is v, + * the number of bits to write is n, + * the offset of the bit is o, + * the max length the bitstream in bits is l + */ + + init_PB32_arrays(testarray0, testarray1); + TEST_ASSERT(testarray0[0] == 0); + TEST_ASSERT(testarray0[1] == 0); + TEST_ASSERT(testarray0[2] == 0); + TEST_ASSERT(testarray1[0] == 0xffffffff); + TEST_ASSERT(testarray1[1] == 0xffffffff); + TEST_ASSERT(testarray1[2] == 0xffffffff); + + /*** n=0 ***/ + + /* do not write, left border */ + v = 0; n = 0; o = 0; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(0, rval); + TEST_ASSERT(testarray0[0] == 0); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(0, rval); + TEST_ASSERT(testarray1[0] == 0xffffffff); + + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(0, rval); + + v = 0xffffffff; n = 0; o = 0; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(0, rval); + TEST_ASSERT(testarray0[0] == 0); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(0, rval); + TEST_ASSERT(testarray1[0] == 0xffffffff); + + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(0, rval); + + /* do not write, right border */ + v = 0; n = 0; o = l; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(l, rval); + TEST_ASSERT(testarray0[0] == 0); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(l, rval); + TEST_ASSERT(testarray1[0] == 0xffffffff); + + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(l, rval); + + /* test value = 0xffffffff; N = 0 */ + v = 0xffffffff; n = 0; o = l; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(l, rval); + TEST_ASSERT(testarray0[0] == 0); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(l, rval); + TEST_ASSERT(testarray1[0] == 0xffffffff); + + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(l, rval); + + /*** n=1 ***/ + + /* left border, write 0 */ + v = 0; n = 1; o = 0; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(1, rval); + TEST_ASSERT(testarray0[0] == 0); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(1, rval); + TEST_ASSERT(testarray1[0] == 0x7fffffff); + + /* left border, write 1 */ + v = 1; n = 1; o = 0; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(1, rval); + TEST_ASSERT(testarray0[0] == 0x80000000); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(1, rval); + TEST_ASSERT(testarray1[0] == 0xffffffff); + + /* left border, write 32 */ + v = 0xf0f0abcd; n = 32; o = 0; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 32); + TEST_ASSERT(testarray0[0] == 0xf0f0abcd); + TEST_ASSERT(testarray0[1] == 0); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(rval, 32); + TEST_ASSERT(testarray1[0] == 0xf0f0abcd); + TEST_ASSERT(testarray1[1] == 0xffffffff); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* middle, write 2 bits */ + v = 3; n = 2; o = 29; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 31); + TEST_ASSERT(testarray0[0] == 0x6); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT(testarray1[0] == 0xffffffff); + TEST_ASSERT_EQUAL_INT(rval, 31); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /*** n=5, unsegmented ***/ + + /* left border, write 0 */ + v = 0; n = 5; o = 0; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 5); + TEST_ASSERT(testarray0[0] == 0); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT(testarray1[0] == 0x07ffffff); + TEST_ASSERT_EQUAL_INT(rval, 5); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* left border, write 11111 */ + v = 0x1f; n = 5; o = 0; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 5); + TEST_ASSERT(testarray0[0] == 0xf8000000); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(rval, 5); + TEST_ASSERT(testarray1[0] == 0xffffffff); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* middle, write 0 */ + v = 0; n = 5; o = 7; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 12); + TEST_ASSERT(testarray0[0] == 0); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(rval, 12); + TEST_ASSERT(testarray1[0] == 0xfe0fffff); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* middle, write 11111 */ + v = 0x1f; n = 5; o = 7; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 12); + TEST_ASSERT(testarray0[0] == 0x01f00000); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(rval, 12); + TEST_ASSERT(testarray1[0] == 0xffffffff); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* right, write 0 */ + v = 0; n = 5; o = 91; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 96); + TEST_ASSERT(testarray0[0] == 0); + TEST_ASSERT(testarray0[1] == 0); + TEST_ASSERT(testarray0[0] == 0); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(rval, 96); + TEST_ASSERT(testarray1[0] == 0xffffffff); + TEST_ASSERT(testarray1[1] == 0xffffffff); + TEST_ASSERT(testarray1[2] == 0xffffffe0); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* right, write 11111 */ + v = 0x1f; n = 5; o = 91; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 96); + TEST_ASSERT(testarray0[0] == 0); + TEST_ASSERT(testarray0[1] == 0); + TEST_ASSERT(testarray0[2] == 0x0000001f); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(rval, 96); + TEST_ASSERT(testarray1[0] == 0xffffffff); + TEST_ASSERT(testarray1[1] == 0xffffffff); + TEST_ASSERT(testarray1[2] == 0xffffffff); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* 32 bit, write 0 */ + v = 0; n = 32; o = 0; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 32); + TEST_ASSERT(testarray0[0] == 0x00000000); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(rval, 32); + TEST_ASSERT(testarray1[0] == 0x00000000); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* 32 bit, write -1 */ + v = 0xffffffff; n = 32; o = 0; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 32); + TEST_ASSERT(testarray0[0] == 0xffffffff); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(rval, 32); + TEST_ASSERT(testarray1[0] == 0xffffffff); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* SEGMENTED cases */ + /* 5 bit, write 0 */ + v = 0; n = 5; o = 62; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 67); + TEST_ASSERT(testarray0[0] == 0); + TEST_ASSERT(testarray0[1] == 0); + TEST_ASSERT(testarray0[2] == 0); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(rval, 67); + TEST_ASSERT(testarray1[0] == 0xffffffff); + TEST_ASSERT(testarray1[1] == 0xfffffffc); + TEST_ASSERT(testarray1[2] == 0x1fffffff); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* 5 bit, write 1f */ + v = 0x1f; n = 5; o = 62; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 67); + TEST_ASSERT(testarray0[0] == 0); + TEST_ASSERT(testarray0[1] == 3); + TEST_ASSERT(testarray0[2] == 0xe0000000); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(rval, 67); + TEST_ASSERT(testarray1[0] == 0xffffffff); + TEST_ASSERT(testarray1[1] == 0xffffffff); + TEST_ASSERT(testarray1[2] == 0xffffffff); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* 32 bit, write 0 */ + v = 0; n = 32; o = 1; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 33); + TEST_ASSERT(testarray0[0] == 0x00000000); + TEST_ASSERT(testarray0[1] == 0x00000000); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(rval, 33); + TEST_ASSERT(testarray1[0] == 0x80000000); + TEST_ASSERT(testarray1[1] == 0x7fffffff); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* 32 bit, write -1 */ + v = 0xffffffff; n = 32; o = 1; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, 33); + TEST_ASSERT(testarray0[0] == 0x7fffffff); + TEST_ASSERT(testarray0[1] == 0x80000000); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(rval, 33); + TEST_ASSERT(testarray1[0] == 0xffffffff); + TEST_ASSERT(testarray1[1] == 0xffffffff); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /* test NULL buffer */ + v = 0; n = 0; o = 0; + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(rval, 0); + + v = 0; n = 1; o = 0; + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(rval, 1); + + v = 0; n = 5; o = 31; + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(rval, 36); + + v = 0; n = 2; o = 95; + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(rval, 97); /* rval can be longer than l */ + + /* value larger than n allows */ + v = 0x7f; n = 6; o = 10; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(16, rval); + TEST_ASSERT(testarray0[0] == 0x003f0000); + TEST_ASSERT(testarray0[1] == 0); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(16, rval); + TEST_ASSERT(testarray1[0] == 0xffffffff); + TEST_ASSERT(testarray1[1] == 0xffffffff); + + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(16, rval); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + v = 0xffffffff; n = 6; o = 10; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(16, rval); + TEST_ASSERT(testarray0[0] == 0x003f0000); + TEST_ASSERT(testarray0[1] == 0); + + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(16, rval); + TEST_ASSERT(testarray1[0] == 0xffffffff); + TEST_ASSERT(testarray1[1] == 0xffffffff); + + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(16, rval); + /* re-init input arrays after clobbering */ + init_PB32_arrays(testarray0, testarray1); + + /*** error cases ***/ + /* n too large */ + v = 0x0; n = 33; o = 1; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(rval, -1); + TEST_ASSERT(testarray0[0] == 0); + TEST_ASSERT(testarray0[1] == 0); + + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(rval, -1); + + /* try to put too much in the bitstream */ + v = 0x1; n = 1; o = 96; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, rval); + TEST_ASSERT(testarray0[0] == 0); + TEST_ASSERT(testarray0[1] == 0); + TEST_ASSERT(testarray0[2] == 0); + + /* this should work (if bitstream=NULL no length check) */ + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(97, rval); + + /* offset lager than max_stream_len(l) */ + v = 0x0; n = 32; o = INT32_MAX; + rval = put_n_bits32(v, n, o, testarray1, l); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, rval); + TEST_ASSERT(testarray1[0] == 0xffffffff); + TEST_ASSERT(testarray1[1] == 0xffffffff); + TEST_ASSERT(testarray1[2] == 0xffffffff); + + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT(rval < 0); + + /* negative offset */ + v = 0x0; n = 0; o = -1; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(-1, rval); + TEST_ASSERT(testarray0[0] == 0); + TEST_ASSERT(testarray0[1] == 0); + + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(-1, rval); + + v = 0x0; n = 0; o = -2; + rval = put_n_bits32(v, n, o, testarray0, l); + TEST_ASSERT_EQUAL_INT(-1, rval); + TEST_ASSERT(testarray0[0] == 0); + TEST_ASSERT(testarray0[1] == 0); + + rval = put_n_bits32(v, n, o, NULL, l); + TEST_ASSERT_EQUAL_INT(-1, rval); +} + + +/** + * @test rice_encoder */ -int clean_suite1(void) + +void test_rice_encoder(void) { - if (0 != fclose(temp_file)) { - return -1; - } - else { - temp_file = NULL; - return 0; - } + uint32_t value, g_par, log2_g_par, cw, cw_len; + + /* test minimum Golomb parameter */ + value = 0; log2_g_par = (uint32_t)ilog_2(MIN_ICU_GOLOMB_PAR); g_par = 1U << log2_g_par; cw = ~0U; + cw_len = rice_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(1, cw_len); + TEST_ASSERT_EQUAL_HEX(0x0, cw); + + value = 31; + cw_len = rice_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFE, cw); + + /* test some arbitrary values */ + value = 0; log2_g_par = 4; g_par = 1U << log2_g_par; cw = ~0U; + cw_len = rice_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(5, cw_len); + TEST_ASSERT_EQUAL_HEX(0x0, cw); + + value = 1; + cw_len = rice_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(5, cw_len); + TEST_ASSERT_EQUAL_HEX(0x1, cw); + + value = 42; + cw_len = rice_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(7, cw_len); + TEST_ASSERT_EQUAL_HEX(0x6a, cw); + + value = 446; + cw_len = rice_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFEE, cw); + + value = 447; + cw_len = rice_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFEF, cw); + + /* test maximum Golomb parameter for rice_encoder */ + value = 0; log2_g_par = (uint32_t)ilog_2(MAX_ICU_GOLOMB_PAR); g_par = 1U << log2_g_par; cw = ~0U; + cw_len = rice_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0x0, cw); + + value = 1; + cw_len = rice_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0x1, cw); + + value = 0x7FFFFFFE; + cw_len = rice_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0x7FFFFFFE, cw); + + value = 0x7FFFFFFF; + cw_len = rice_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0x7FFFFFFF, cw); } -/* Simple test of fprintf(). - * Writes test data to the temporary file and checks - * whether the expected number of bytes were written. + +/** + * @test golomb_encoder */ -void testFPRINTF(void) + +void test_Golomb_encoder(void) { - int i1 = 10; + uint32_t value, g_par, log2_g_par, cw, cw_len; - if (NULL != temp_file) { - CU_ASSERT(0 == fprintf(temp_file, "")); - CU_ASSERT(2 == fprintf(temp_file, "Q\n")); - CU_ASSERT(7 == fprintf(temp_file, "i1 = %d", i1)); - } + /* test minimum Golomb parameter */ + value = 0; g_par = MIN_ICU_GOLOMB_PAR; log2_g_par = (uint32_t)ilog_2(g_par); cw = ~0U; + cw_len = golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(1, cw_len); + TEST_ASSERT_EQUAL_HEX(0x0, cw); + + value = 31; + cw_len = golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFE, cw); + + + /* test some arbitrary values with g_par = 16 */ + value = 0; g_par = 16; log2_g_par = (uint32_t)ilog_2(g_par); cw = ~0U; + cw_len = golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(5, cw_len); + TEST_ASSERT_EQUAL_HEX(0x0, cw); + + value = 1; + cw_len = golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(5, cw_len); + TEST_ASSERT_EQUAL_HEX(0x1, cw); + + value = 42; + cw_len = golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(7, cw_len); + TEST_ASSERT_EQUAL_HEX(0x6a, cw); + + value = 446; + cw_len = golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFEE, cw); + + value = 447; + cw_len = golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFEF, cw); + + + /* test some arbitrary values with g_par = 3 */ + value = 0; g_par = 3; log2_g_par = (uint32_t)ilog_2(g_par); cw = ~0U; + cw_len = golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(2, cw_len); + TEST_ASSERT_EQUAL_HEX(0x0, cw); + + value = 1; + cw_len = golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(3, cw_len); + TEST_ASSERT_EQUAL_HEX(0x2, cw); + + value = 42; + cw_len = golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(16, cw_len); + TEST_ASSERT_EQUAL_HEX(0xFFFC, cw); + + value = 44; + cw_len = golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(17, cw_len); + TEST_ASSERT_EQUAL_HEX(0x1FFFB, cw); + + value = 88; + cw_len = golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFA, cw); + + value = 89; + cw_len = golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFB, cw); + + + /* test maximum Golomb parameter for golomb_encoder */ + value = 0; g_par = MAX_ICU_GOLOMB_PAR; log2_g_par = (uint32_t)ilog_2(g_par); cw = ~0U; + cw_len = golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0x0, cw); + + value = 1; g_par = MAX_ICU_GOLOMB_PAR; log2_g_par = (uint32_t)ilog_2(g_par); cw = ~0U; + cw_len = golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0x1, cw); + + value = 0x7FFFFFFE; + cw_len = golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0x7FFFFFFE, cw); + + value = 0x7FFFFFFF; + cw_len = golomb_encoder(value, g_par, log2_g_par, &cw); + TEST_ASSERT_EQUAL_INT(32, cw_len); + TEST_ASSERT_EQUAL_HEX(0x7FFFFFFF, cw); } -/* Simple test of fread(). - * Reads the data previously written by testFPRINTF() - * and checks whether the expected characters are present. - * Must be run after testFPRINTF(). + +/** + * @test encode_value_zero */ -void testFREAD(void) + +void test_encode_value_zero(void) { - unsigned char buffer[20]; + uint32_t data, model; + int stream_len; + struct encoder_setupt setup = {0}; + uint32_t bitstream[3] = {0}; - if (NULL != temp_file) { - rewind(temp_file); - CU_ASSERT(9 == fread(buffer, sizeof(unsigned char), 20, temp_file)); - CU_ASSERT(0 == strncmp(buffer, "Q\ni1 = 10", 9)); - } + /* setup the setup */ + setup.encoder_par1 = 1; + setup.encoder_par2 = (uint32_t)ilog_2(setup.encoder_par1); + setup.spillover_par = 32; + setup.max_data_bits = 32; + setup.generate_cw_f = rice_encoder; + setup.bitstream_adr = bitstream; + setup.max_stream_len = sizeof(bitstream) * CHAR_BIT; + + stream_len = 0; + + data = 0; model = 0; + stream_len = encode_value_zero(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(2, stream_len); + TEST_ASSERT_EQUAL_HEX(0x80000000, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); + + data = 5; model = 0; + stream_len = encode_value_zero(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(14, stream_len); + TEST_ASSERT_EQUAL_HEX(0xBFF80000, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); + + data = 2; model = 7; + stream_len = encode_value_zero(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(25, stream_len); + TEST_ASSERT_EQUAL_HEX(0xBFFBFF00, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); + + /* zero escape mechanism */ + data = 100; model = 42; + /* (100-42)*2+1=117 -> cw 0 + 0x0000_0000_0000_0075 */ + stream_len = encode_value_zero(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(58, stream_len); + TEST_ASSERT_EQUAL_HEX(0xBFFBFF00, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0x00001D40, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); + + /* test overflow */ + data = INT32_MIN; model = 0; + /* (INT32_MIN)*-2-1+1=0(overflow) -> cw 0 + 0x0000_0000_0000_0000 */ + stream_len = encode_value_zero(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(91, stream_len); + TEST_ASSERT_EQUAL_HEX(0xBFFBFF00, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0x00001D40, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); + + /* small buffer error */ + data = 23; model = 26; + stream_len = encode_value_zero(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, stream_len); + + /* reset bitstream to all bits set */ + bitstream[0] = ~0U; + bitstream[1] = ~0U; + bitstream[2] = ~0U; + stream_len = 0; + + /* we use now values with maximum 6 bits */ + setup.max_data_bits = 6; + + /* lowest value before zero encoding */ + data = 53; model = 38; + stream_len = encode_value_zero(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(32, stream_len); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFE, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFF, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFF, bitstream[2]); + + /* lowest value with zero encoding */ + data = 0; model = 16; + stream_len = encode_value_zero(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(39, stream_len); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFE, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0x41FFFFFF, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFF, bitstream[2]); + + /* maximum positive value to encode */ + data = 31; model = 0; + stream_len = encode_value_zero(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(46, stream_len); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFE, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0x40FFFFFF, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFF, bitstream[2]); + + /* maximum negative value to encode */ + data = 0; model = 32; + stream_len = encode_value_zero(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(53, stream_len); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFE, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0x40FC07FF, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFF, bitstream[2]); + + /* small buffer error when creating the zero escape symbol*/ + bitstream[0] = 0; + bitstream[1] = 0; + bitstream[2] = 0; + stream_len = 32; + setup.max_stream_len = 32; + data = 31; model = 0; + stream_len = encode_value_zero(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, stream_len); + TEST_ASSERT_EQUAL_HEX(0, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0, bitstream[2]); } -/* The main() function for setting up and running the tests. - * Returns a CUE_SUCCESS on successful running, another - * CUnit error code on failure. + +/** + * @test encode_value_multi */ -int main() + +void test_encode_value_multi(void) { - CU_pSuite pSuite = NULL; + uint32_t data, model; + int stream_len; + struct encoder_setupt setup = {0}; + uint32_t bitstream[4] = {0}; - /* initialize the CUnit test registry */ - if (CUE_SUCCESS != CU_initialize_registry()) - return CU_get_error(); + /* setup the setup */ + setup.encoder_par1 = 1; + setup.encoder_par2 = (uint32_t)ilog_2(setup.encoder_par1); + setup.spillover_par = 16; + setup.max_data_bits = 32; + setup.generate_cw_f = golomb_encoder; + setup.bitstream_adr = bitstream; + setup.max_stream_len = sizeof(bitstream) * CHAR_BIT; - /* add a suite to the registry */ - pSuite = CU_add_suite("Suite_1", init_suite1, clean_suite1); - if (NULL == pSuite) { - CU_cleanup_registry(); - return CU_get_error(); - } + stream_len = 0; + + data = 0; model = 0; + stream_len = encode_value_multi(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(1, stream_len); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[3]); + + data = 0; model = 1; + stream_len = encode_value_multi(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(3, stream_len); + TEST_ASSERT_EQUAL_HEX(0x40000000, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[3]); + + data = 1+23; model = 0+23; + stream_len = encode_value_multi(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(6, stream_len); + TEST_ASSERT_EQUAL_HEX(0x58000000, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[3]); + + /* highest value without multi outlier encoding */ + data = 0+42; model = 8+42; + stream_len = encode_value_multi(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(22, stream_len); + TEST_ASSERT_EQUAL_HEX(0x5BFFF800, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[3]); + + /* lowest value with multi outlier encoding */ + data = 8+42; model = 0+42; + stream_len = encode_value_multi(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(41, stream_len); + TEST_ASSERT_EQUAL_HEX(0x5BFFFBFF, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0xFC000000, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[3]); + + /* highest value with multi outlier encoding */ + data = INT32_MIN; model = 0; + stream_len = encode_value_multi(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(105, stream_len); + TEST_ASSERT_EQUAL_HEX(0x5BFFFBFF, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0xFC7FFFFF, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0xFF7FFFFF, bitstream[2]); + TEST_ASSERT_EQUAL_HEX(0xF7800000, bitstream[3]); + + /* small buffer error */ + data = 0; model = 38; + stream_len = encode_value_multi(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, stream_len); + + /* small buffer error when creating the multi escape symbol*/ + bitstream[0] = 0; + bitstream[1] = 0; + setup.max_stream_len = 32; + + stream_len = 32; + data = 31; model = 0; + stream_len = encode_value_multi(data, model, stream_len, &setup); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, stream_len); + TEST_ASSERT_EQUAL_HEX(0, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0, bitstream[1]); +} + + +/** + * @test encode_value + */ + +void test_encode_value(void) +{ + struct encoder_setupt setup = {0}; + uint32_t bitstream[4] = {0}; + uint32_t data, model; + int cmp_size; + + setup.encode_method_f = encode_value_none; + setup.bitstream_adr = bitstream; + setup.max_stream_len = 128; + cmp_size = 0; + + /* test 32 bit input */ + setup.encoder_par1 = 32; + setup.max_data_bits = 32; + setup.lossy_par = 0; + + data = 0; model = 0; + cmp_size = encode_value(data, model, cmp_size, &setup); + TEST_ASSERT_EQUAL_INT(32, cmp_size); + TEST_ASSERT_EQUAL_HEX(0, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0, bitstream[2]); + TEST_ASSERT_EQUAL_HEX(0, bitstream[3]); + + data = UINT32_MAX; model = 0; + cmp_size = encode_value(data, model, cmp_size, &setup); + TEST_ASSERT_EQUAL_INT(64, cmp_size); + TEST_ASSERT_EQUAL_HEX(0, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFF, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0, bitstream[2]); + TEST_ASSERT_EQUAL_HEX(0, bitstream[3]); + + /* test rounding */ + setup.lossy_par = 1; + data = UINT32_MAX; model = 0; + cmp_size = encode_value(data, model, cmp_size, &setup); + TEST_ASSERT_EQUAL_INT(96, cmp_size); + TEST_ASSERT_EQUAL_HEX(0, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFF, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0x7FFFFFFF, bitstream[2]); + TEST_ASSERT_EQUAL_HEX(0, bitstream[3]); + + setup.lossy_par = 2; + data = 0x3; model = 0; + cmp_size = encode_value(data, model, cmp_size, &setup); + TEST_ASSERT_EQUAL_INT(128, cmp_size); + TEST_ASSERT_EQUAL_HEX(0, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFF, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0x7FFFFFFF, bitstream[2]); + TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[3]); + + /* small buffer error bitstream can not hold more data*/ + cmp_size = encode_value(data, model, cmp_size, &setup); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, cmp_size); + + /* reset bitstream */ + bitstream[0] = 0; + bitstream[1] = 0; + bitstream[2] = 0; + bitstream[3] = 0; + cmp_size = 0; + + /* test 31 bit input */ + setup.encoder_par1 = 31; + setup.max_data_bits = 31; + setup.lossy_par = 0; + + data = 0; model = 0; + cmp_size = encode_value(data, model, cmp_size, &setup); + TEST_ASSERT_EQUAL_INT(31, cmp_size); + TEST_ASSERT_EQUAL_HEX(0, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0, bitstream[2]); + TEST_ASSERT_EQUAL_HEX(0, bitstream[3]); + + data = 0x7FFFFFFF; model = 0; + cmp_size = encode_value(data, model, cmp_size, &setup); + TEST_ASSERT_EQUAL_INT(62, cmp_size); + TEST_ASSERT_EQUAL_HEX(0x00000001, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFC, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0, bitstream[2]); + TEST_ASSERT_EQUAL_HEX(0, bitstream[3]); + + /* round = 1 */ + setup.lossy_par = 1; + data = UINT32_MAX; model = UINT32_MAX; + cmp_size = encode_value(data, model, cmp_size, &setup); + TEST_ASSERT_EQUAL_INT(93, cmp_size); + TEST_ASSERT_EQUAL_HEX(0x00000001, bitstream[0]); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFFF, bitstream[1]); + TEST_ASSERT_EQUAL_HEX(0xFFFFFFF8, bitstream[2]); + TEST_ASSERT_EQUAL_HEX(0, bitstream[3]); + + /* data are bigger than max_data_bits */ + setup.lossy_par = 0; + data = UINT32_MAX; model = 0; + cmp_size = encode_value(data, model, cmp_size, &setup); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_size); + + /* model are bigger than max_data_bits */ + setup.lossy_par = 0; + cmp_size = 93; + data = 0; model = UINT32_MAX; + cmp_size = encode_value(data, model, cmp_size, &setup); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_size); +} + + +/** + * @test cmp_get_max_used_bits + */ + +void test_cmp_get_max_used_bits(void) +{ + struct cmp_max_used_bits max_used_bits = cmp_get_max_used_bits(); + + TEST_ASSERT_EQUAL_INT(max_used_bits.nc_imagette, MAX_USED_NC_IMAGETTE_BITS); + TEST_ASSERT_EQUAL_INT(max_used_bits.saturated_imagette, MAX_USED_SATURATED_IMAGETTE_BITS); + TEST_ASSERT_EQUAL_INT(max_used_bits.fc_imagette, MAX_USED_FC_IMAGETTE_BITS); + + TEST_ASSERT_EQUAL_INT(max_used_bits.f_fx, MAX_USED_F_FX_BITS); + TEST_ASSERT_EQUAL_INT(max_used_bits.f_efx, MAX_USED_F_EFX_BITS); + TEST_ASSERT_EQUAL_INT(max_used_bits.f_ncob, MAX_USED_F_NCOB_BITS); + TEST_ASSERT_EQUAL_INT(max_used_bits.f_ecob, MAX_USED_F_ECOB_BITS); + + TEST_ASSERT_EQUAL_INT(max_used_bits.s_exp_flags, MAX_USED_S_FX_EXPOSURE_FLAGS_BITS); + TEST_ASSERT_EQUAL_INT(max_used_bits.s_fx, MAX_USED_S_FX_BITS); + TEST_ASSERT_EQUAL_INT(max_used_bits.s_efx, MAX_USED_S_EFX_BITS); + TEST_ASSERT_EQUAL_INT(max_used_bits.s_ncob, MAX_USED_S_NCOB_BITS); + TEST_ASSERT_EQUAL_INT(max_used_bits.s_ecob, MAX_USED_S_ECOB_BITS); + + TEST_ASSERT_EQUAL_INT(max_used_bits.l_fx_variance, MAX_USED_L_FX_VARIANCE_BITS); + TEST_ASSERT_EQUAL_INT(max_used_bits.l_efx, MAX_USED_L_EFX_BITS); + TEST_ASSERT_EQUAL_INT(max_used_bits.l_ncob, MAX_USED_L_NCOB_BITS); + TEST_ASSERT_EQUAL_INT(max_used_bits.l_ecob, MAX_USED_L_ECOB_BITS); + TEST_ASSERT_EQUAL_INT(max_used_bits.l_cob_variance, MAX_USED_L_COB_VARIANCE_BITS); + + TEST_ASSERT_EQUAL_INT(max_used_bits.nc_offset_mean, MAX_USED_NC_OFFSET_MEAN_BITS); + TEST_ASSERT_EQUAL_INT(max_used_bits.nc_offset_variance, MAX_USED_NC_OFFSET_VARIANCE_BITS); - /* add the tests to the suite */ - /* NOTE - ORDER IS IMPORTANT - MUST TEST fread() AFTER fprintf() */ - if ((NULL == CU_add_test(pSuite, "test of fprintf()", testFPRINTF)) || - (NULL == CU_add_test(pSuite, "test of fread()", testFREAD))) - { - CU_cleanup_registry(); - return CU_get_error(); + TEST_ASSERT_EQUAL_INT(max_used_bits.nc_background_mean, MAX_USED_NC_BACKGROUND_MEAN_BITS); + TEST_ASSERT_EQUAL_INT(max_used_bits.nc_background_variance, MAX_USED_NC_BACKGROUND_VARIANCE_BITS); + TEST_ASSERT_EQUAL_INT(max_used_bits.nc_background_outlier_pixels, MAX_USED_NC_BACKGROUND_OUTLIER_PIXELS_BITS); + + TEST_ASSERT_EQUAL_INT(max_used_bits.smeating_mean, MAX_USED_SMEARING_MEAN_BITS); + TEST_ASSERT_EQUAL_INT(max_used_bits.smeating_variance_mean, MAX_USED_SMEARING_VARIANCE_MEAN_BITS); + TEST_ASSERT_EQUAL_INT(max_used_bits.smearing_outlier_pixels, MAX_USED_SMEARING_OUTLIER_PIXELS_BITS); + + TEST_ASSERT_EQUAL_INT(max_used_bits.fc_offset_mean, MAX_USED_FC_OFFSET_MEAN_BITS); + TEST_ASSERT_EQUAL_INT(max_used_bits.fc_offset_variance, MAX_USED_FC_OFFSET_VARIANCE_BITS); + TEST_ASSERT_EQUAL_INT(max_used_bits.fc_offset_pixel_in_error, MAX_USED_FC_OFFSET_PIXEL_IN_ERROR_BITS); + + TEST_ASSERT_EQUAL_INT(max_used_bits.fc_background_mean, MAX_USED_FC_BACKGROUND_MEAN_BITS); + TEST_ASSERT_EQUAL_INT(max_used_bits.fc_background_variance, MAX_USED_FC_BACKGROUND_VARIANCE_BITS); + TEST_ASSERT_EQUAL_INT(max_used_bits.fc_background_outlier_pixels, MAX_USED_FC_BACKGROUND_OUTLIER_PIXELS_BITS); +} + + +void test_compress_imagette_diff(void) +{ + uint16_t data[] = {0xFFFF, 1, 0, 42, 0x8000, 0x7FFF, 0xFFFF}; + uint32_t output_buf[3] = {0xFFFF, 0xFFFF, 0xFFFF}; + struct cmp_cfg cfg = {0}; + int cmp_size; + + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.cmp_mode = CMP_MODE_DIFF_ZERO; + cfg.input_buf = data; + cfg.samples = 7; + cfg.golomb_par = 1; + cfg.spill = 8; + cfg.icu_output_buf = (uint32_t *)output_buf; + cfg.buffer_length = 7; + + cmp_size = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(66, cmp_size); + TEST_ASSERT_EQUAL_HEX(0xDF6002AB, be32_to_cpu(output_buf[0])); + TEST_ASSERT_EQUAL_HEX(0xFEB70000, be32_to_cpu(output_buf[1])); + TEST_ASSERT_EQUAL_HEX(0x00000000, be32_to_cpu(output_buf[2])); +} + +void test_compress_imagette_model(void) +{ + uint16_t data[] = {0x0000, 0x0001, 0x0042, 0x8000, 0x7FFF, 0xFFFF, 0xFFFF}; + uint16_t model[] = {0x0000, 0xFFFF, 0xF301, 0x8FFF, 0x0000, 0xFFFF, 0x0000}; + uint16_t model_up[7] = {0}; + uint32_t output_buf[3] = {0xFFFF, 0xFFFF, 0xFFFF}; + struct cmp_cfg cfg = {0}; + int cmp_size; + + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.cmp_mode = CMP_MODE_MODEL_MULTI; + cfg.input_buf = data; + cfg.model_buf = model; + cfg.icu_new_model_buf = model_up; + cfg.samples = 7; + cfg.golomb_par = 3; + cfg.spill = 8; + cfg.model_value = 8; + cfg.icu_output_buf = (uint32_t *)output_buf; + cfg.buffer_length = 8; + + cmp_size = icu_compress_data(&cfg); + + TEST_ASSERT_EQUAL_INT(76, cmp_size); + TEST_ASSERT_EQUAL_HEX(0x2BDB4F5E, be32_to_cpu(output_buf[0])); + TEST_ASSERT_EQUAL_HEX(0xDFF5F9FF, be32_to_cpu(output_buf[1])); + TEST_ASSERT_EQUAL_HEX(0xEC200000, be32_to_cpu(output_buf[2])); + + TEST_ASSERT_EQUAL_HEX(0x0000, model_up[0]); + TEST_ASSERT_EQUAL_HEX(0x8000, model_up[1]); + TEST_ASSERT_EQUAL_HEX(0x79A1, model_up[2]); + TEST_ASSERT_EQUAL_HEX(0x87FF, model_up[3]); + TEST_ASSERT_EQUAL_HEX(0x3FFF, model_up[4]); + TEST_ASSERT_EQUAL_HEX(0xFFFF, model_up[5]); + TEST_ASSERT_EQUAL_HEX(0x7FFF, model_up[6]); +} + + +void test_compress_imagette_stuff(void) +{ + uint16_t data[] = {0x0, 0x1, 0x23, 0x42, 0x8000, 0x7FFF, 0xFFFF}; + uint32_t output_buf[4] = {0}; + struct cmp_cfg cfg = {0}; + + int cmp_size; + uint8_t output_buf_exp[] = { + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x23, 0x00, 0x42, + 0x80, 0x00, 0x7F, 0xFF, + 0xFF, 0xFF, 0x00, 0x00}; + + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.cmp_mode = CMP_MODE_STUFF; + cfg.input_buf = data; + cfg.samples = 7; + cfg.icu_output_buf = (uint32_t *)output_buf; + cfg.buffer_length = 8; + cfg.golomb_par = 16; /* how many used bits has the maximum data value */ + + cmp_size = icu_compress_data(&cfg); + + uint32_t *output_buf_exp_32 = (uint32_t *)output_buf_exp; + TEST_ASSERT_EQUAL_INT(7*16, cmp_size); + TEST_ASSERT_EQUAL_HEX16(output_buf_exp_32[0], output_buf[0]); + TEST_ASSERT_EQUAL_HEX16(output_buf_exp_32[1], output_buf[1]); + TEST_ASSERT_EQUAL_HEX16(output_buf_exp_32[2], output_buf[2]); + TEST_ASSERT_EQUAL_HEX16(output_buf_exp_32[3], output_buf[3]); +} + + +void test_compress_imagette_raw(void) +{ + uint16_t data[] = {0x0, 0x1, 0x23, 0x42, INT16_MIN, INT16_MAX, UINT16_MAX}; + uint16_t output_buf[7] = {0}; + struct cmp_cfg cfg = {0}; + + int cmp_size; + + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.model_buf = NULL; + cfg.input_buf = data; + cfg.samples = 7; + cfg.icu_output_buf = (uint32_t *)output_buf; + cfg.buffer_length = 7; + + + cmp_size = icu_compress_data(&cfg); + + TEST_ASSERT_EQUAL_INT(7*16, cmp_size); + TEST_ASSERT_EQUAL_HEX16(0x0, be16_to_cpu(output_buf[0])); + TEST_ASSERT_EQUAL_HEX16(0x1, be16_to_cpu(output_buf[1])); + TEST_ASSERT_EQUAL_HEX16(0x23, be16_to_cpu(output_buf[2])); + TEST_ASSERT_EQUAL_HEX16(0x42, be16_to_cpu(output_buf[3])); + TEST_ASSERT_EQUAL_HEX16(INT16_MIN, be16_to_cpu(output_buf[4])); + TEST_ASSERT_EQUAL_HEX16(INT16_MAX, be16_to_cpu(output_buf[5])); + TEST_ASSERT_EQUAL_HEX16(UINT16_MAX, be16_to_cpu(output_buf[6])); +} + + +void test_compress_s_fx_raw(void) +{ + struct s_fx data[7]; + struct cmp_cfg cfg = {0}; + int cmp_size, cmp_size_exp; + size_t i; + + cfg.data_type = DATA_TYPE_S_FX; + cfg.model_buf = NULL; + cfg.samples = 7; + cfg.input_buf = malloc(cmp_cal_size_of_data(cfg.samples, cfg.data_type)); + cfg.buffer_length = 7; + cfg.icu_output_buf = malloc(cmp_cal_size_of_data(cfg.buffer_length, cfg.data_type)); + TEST_ASSERT_NOT_NULL(cfg.icu_output_buf); + TEST_ASSERT_NOT_NULL(cfg.input_buf); + + data[0].exp_flags = 0x0; + data[0].fx = 0x0; + data[1].exp_flags = 0x1; + data[1].fx = 0x1; + data[2].exp_flags = 0x2; + data[2].fx = 0x23; + data[3].exp_flags = 0x3; + data[3].fx = 0x42; + data[4].exp_flags = 0x0; + data[4].fx = INT32_MIN; + data[5].exp_flags = 0x3; + data[5].fx = INT32_MAX; + data[6].exp_flags = 0x1; + data[6].fx = UINT32_MAX; + + struct multi_entry_hdr *hdr = cfg.input_buf; + memset(hdr, 0x42, sizeof(struct multi_entry_hdr)); + memcpy(hdr->entry, data, sizeof(data)); + + cmp_size = icu_compress_data(&cfg); + + cmp_size_exp = (sizeof(data) + sizeof(struct multi_entry_hdr)) * CHAR_BIT; + TEST_ASSERT_EQUAL_INT(cmp_size_exp, cmp_size); + + for (i = 0; i < ARRAY_SIZE(data); i++) { + hdr = (struct multi_entry_hdr *)cfg.icu_output_buf; + struct s_fx *p = (struct s_fx *)hdr->entry; + + TEST_ASSERT_EQUAL_HEX(data[i].exp_flags, p[i].exp_flags); + TEST_ASSERT_EQUAL_HEX(data[i].fx, cpu_to_be32(p[i].fx)); } +} + + +void test_compress_s_fx_staff(void) +{ + struct s_fx data[5]; + struct cmp_cfg cfg = {0}; + int cmp_size, cmp_size_exp; + struct multi_entry_hdr *hdr; + uint32_t *cmp_data; + + /* setup configuration */ + cfg.data_type = DATA_TYPE_S_FX; + cfg.cmp_mode = CMP_MODE_STUFF; + cfg.samples = 5; + cfg.input_buf = malloc(cmp_cal_size_of_data(cfg.samples, cfg.data_type)); + TEST_ASSERT_NOT_NULL(cfg.input_buf); + cfg.buffer_length = 5; + cfg.icu_output_buf = malloc(cmp_cal_size_of_data(cfg.buffer_length, cfg.data_type)); + TEST_ASSERT_NOT_NULL(cfg.icu_output_buf); + cfg.cmp_par_exp_flags = 2; + cfg.cmp_par_fx = 21; + + /* generate input data */ + hdr = cfg.input_buf; + /* use dummy data for the header */ + memset(hdr, 0x42, sizeof(struct multi_entry_hdr)); + data[0].exp_flags = 0x0; + data[0].fx = 0x0; + data[1].exp_flags = 0x1; + data[1].fx = 0x1; + data[2].exp_flags = 0x2; + data[2].fx = 0x23; + data[3].exp_flags = 0x3; + data[3].fx = 0x42; + data[4].exp_flags = 0x0; + data[4].fx = 0x001FFFFF; + memcpy(hdr->entry, data, sizeof(data)); + + cmp_size = icu_compress_data(&cfg); - /* Run all tests using the CUnit Basic interface */ - CU_basic_set_mode(CU_BRM_VERBOSE); - CU_basic_run_tests(); - CU_cleanup_registry(); - return CU_get_error(); + cmp_size_exp = 5 * (2 + 21) + MULTI_ENTRY_HDR_SIZE * CHAR_BIT; + TEST_ASSERT_EQUAL_INT(cmp_size_exp, cmp_size); + TEST_ASSERT_FALSE(memcmp(cfg.input_buf, cfg.icu_output_buf, MULTI_ENTRY_HDR_SIZE)); + hdr = (void *)cfg.icu_output_buf; + cmp_data = (uint32_t *)hdr->entry; + TEST_ASSERT_EQUAL_HEX(0x00000080, be32_to_cpu(cmp_data[0])); + TEST_ASSERT_EQUAL_HEX(0x00060001, be32_to_cpu(cmp_data[1])); + TEST_ASSERT_EQUAL_HEX(0x1E000423, be32_to_cpu(cmp_data[2])); + TEST_ASSERT_EQUAL_HEX(0xFFFFE000, be32_to_cpu(cmp_data[3])); + + free(cfg.input_buf); + free(cfg.icu_output_buf); +} + + +void test_compress_s_fx_model_multi(void) +{ + struct s_fx data[6], model[6]; + struct s_fx *up_model_buf; + struct cmp_cfg cfg = {0}; + int cmp_size; + struct multi_entry_hdr *hdr; + uint32_t *cmp_data; + struct cmp_max_used_bits max_used_bits; + + /* setup configuration */ + cfg.data_type = DATA_TYPE_S_FX; + cfg.cmp_mode = CMP_MODE_MODEL_MULTI; + cfg.model_value = 11; + cfg.samples = 6; + cfg.input_buf = malloc(cmp_cal_size_of_data(cfg.samples, cfg.data_type)); + TEST_ASSERT_NOT_NULL(cfg.input_buf); + cfg.model_buf = malloc(cmp_cal_size_of_data(cfg.samples, cfg.data_type)); + TEST_ASSERT_NOT_NULL(cfg.model_buf); + cfg.icu_new_model_buf = malloc(cmp_cal_size_of_data(cfg.samples, cfg.data_type)); + TEST_ASSERT_NOT_NULL(cfg.icu_new_model_buf); + cfg.buffer_length = 6; + cfg.icu_output_buf = malloc(cmp_cal_size_of_data(cfg.buffer_length, cfg.data_type)); + TEST_ASSERT_NOT_NULL(cfg.icu_output_buf); + cfg.cmp_par_exp_flags = 1; + cfg.spill_exp_flags = 8; + cfg.cmp_par_fx = 3; + cfg.spill_fx = 35; + + + /* generate input data */ + hdr = cfg.input_buf; + /* use dummy data for the header */ + memset(hdr, 0x42, sizeof(struct multi_entry_hdr)); + data[0].exp_flags = 0x0; + data[0].fx = 0x0; + data[1].exp_flags = 0x1; + data[1].fx = 0x1; + data[2].exp_flags = 0x2; + data[2].fx = 0x23; + data[3].exp_flags = 0x3; + data[3].fx = 0x42; + data[4].exp_flags = 0x0; + data[4].fx = 0x001FFFFF; + data[5].exp_flags = 0x0; + data[5].fx = 0x0; + memcpy(hdr->entry, data, sizeof(data)); + + /* generate model data */ + hdr = cfg.model_buf; + /* use dummy data for the header */ + memset(hdr, 0x41, sizeof(struct multi_entry_hdr)); + model[0].exp_flags = 0x0; + model[0].fx = 0x0; + model[1].exp_flags = 0x3; + model[1].fx = 0x1; + model[2].exp_flags = 0x0; + model[2].fx = 0x42; + model[3].exp_flags = 0x0; + model[3].fx = 0x23; + model[4].exp_flags = 0x3; + model[4].fx = 0x0; + model[5].exp_flags = 0x2; + model[5].fx = 0x001FFFFF; + memcpy(hdr->entry, model, sizeof(model)); + + max_used_bits = cmp_get_max_used_bits(); + max_used_bits.s_exp_flags = 2; + max_used_bits.s_fx = 21; + cmp_set_max_used_bits(&max_used_bits); + + cmp_size = icu_compress_data(&cfg); + + TEST_ASSERT_EQUAL_INT(166, cmp_size); + TEST_ASSERT_FALSE(memcmp(cfg.input_buf, cfg.icu_output_buf, MULTI_ENTRY_HDR_SIZE)); + cmp_data = &cfg.icu_output_buf[MULTI_ENTRY_HDR_SIZE/sizeof(uint32_t)]; + TEST_ASSERT_EQUAL_HEX(0x1C77FFA6, be32_to_cpu(cmp_data[0])); + TEST_ASSERT_EQUAL_HEX(0xAFFF4DE5, be32_to_cpu(cmp_data[1])); + TEST_ASSERT_EQUAL_HEX(0xCC000000, be32_to_cpu(cmp_data[2])); + + TEST_ASSERT_FALSE(memcmp(cfg.input_buf, cfg.icu_output_buf, MULTI_ENTRY_HDR_SIZE)); + hdr = cfg.icu_new_model_buf; + up_model_buf = (struct s_fx *)hdr->entry; + TEST_ASSERT_EQUAL_HEX(0x0, up_model_buf[0].exp_flags); + TEST_ASSERT_EQUAL_HEX(0x0, up_model_buf[0].fx); + TEST_ASSERT_EQUAL_HEX(0x2, up_model_buf[1].exp_flags); + TEST_ASSERT_EQUAL_HEX(0x1, up_model_buf[1].fx); + TEST_ASSERT_EQUAL_HEX(0x0, up_model_buf[2].exp_flags); + TEST_ASSERT_EQUAL_HEX(0x38, up_model_buf[2].fx); + TEST_ASSERT_EQUAL_HEX(0x0, up_model_buf[3].exp_flags); + TEST_ASSERT_EQUAL_HEX(0x2C, up_model_buf[3].fx); + TEST_ASSERT_EQUAL_HEX(0x2, up_model_buf[4].exp_flags); + TEST_ASSERT_EQUAL_HEX(0x9FFFF, up_model_buf[4].fx); + TEST_ASSERT_EQUAL_HEX(0x1, up_model_buf[5].exp_flags); + TEST_ASSERT_EQUAL_HEX(0x15FFFF, up_model_buf[5].fx); + + free(cfg.input_buf); + free(cfg.model_buf); + free(cfg.icu_new_model_buf); + free(cfg.icu_output_buf); } + + diff --git a/test/cmp_icu/test_cmp_icu_new.c b/test/cmp_icu/test_cmp_icu_new.c deleted file mode 100644 index a0d9109..0000000 --- a/test/cmp_icu/test_cmp_icu_new.c +++ /dev/null @@ -1,894 +0,0 @@ -#include <string.h> - -#include "unity.h" - -#include "cmp_support.h" -/* this is a hack to test static functions */ -#include "../lib/cmp_icu_new.c" - - -/** - * @test map_to_pos - */ - -void test_map_to_pos(void) -{ - uint32_t value_to_map; - uint32_t max_value_bits; - uint32_t mapped_value; - - /* test mapping 32 bits values */ - max_value_bits = 32; - - value_to_map = 0; - mapped_value = map_to_pos(value_to_map, max_value_bits); - TEST_ASSERT_EQUAL_INT(0, mapped_value); - - value_to_map = -1U; - mapped_value = map_to_pos(value_to_map, max_value_bits); - TEST_ASSERT_EQUAL_INT(1, mapped_value); - - value_to_map = 1; - mapped_value = map_to_pos(value_to_map, max_value_bits); - TEST_ASSERT_EQUAL_INT(2, mapped_value); - - value_to_map = 42; - mapped_value = map_to_pos(value_to_map, max_value_bits); - TEST_ASSERT_EQUAL_INT(84, mapped_value); - - value_to_map = INT32_MAX; - mapped_value = map_to_pos(value_to_map, max_value_bits); - TEST_ASSERT_EQUAL_HEX(UINT32_MAX-1, mapped_value); - - value_to_map = INT32_MIN; - mapped_value = map_to_pos(value_to_map, max_value_bits); - TEST_ASSERT_EQUAL_HEX(UINT32_MAX, mapped_value); - - /* test mapping 16 bits values */ - max_value_bits = 16; - - value_to_map = -1U; - mapped_value = map_to_pos(value_to_map, max_value_bits); - TEST_ASSERT_EQUAL_INT(1, mapped_value); - - /* test mapping 6 bits values */ - max_value_bits = 6; - - value_to_map = 0; - mapped_value = map_to_pos(value_to_map, max_value_bits); - TEST_ASSERT_EQUAL_INT(0, mapped_value); - - value_to_map = -1U; - mapped_value = map_to_pos(value_to_map, max_value_bits); - TEST_ASSERT_EQUAL_INT(1, mapped_value); - - value_to_map = UINT32_MAX; - mapped_value = map_to_pos(value_to_map, max_value_bits); - TEST_ASSERT_EQUAL_INT(1, mapped_value); - - value_to_map = -1U & 0x3FU; - mapped_value = map_to_pos(value_to_map, max_value_bits); - TEST_ASSERT_EQUAL_INT(1, mapped_value); - - value_to_map = 63; - mapped_value = map_to_pos(value_to_map, max_value_bits); - TEST_ASSERT_EQUAL_INT(1, mapped_value); - - value_to_map = 1; - mapped_value = map_to_pos(value_to_map, max_value_bits); - TEST_ASSERT_EQUAL_INT(2, mapped_value); - - value_to_map = 31; - mapped_value = map_to_pos(value_to_map, max_value_bits); - TEST_ASSERT_EQUAL_INT(62, mapped_value); - - value_to_map = -33U; /* aka 31 */ - mapped_value = map_to_pos(value_to_map, max_value_bits); - TEST_ASSERT_EQUAL_INT(62, mapped_value); - - value_to_map = -32U; - mapped_value = map_to_pos(value_to_map, max_value_bits); - TEST_ASSERT_EQUAL_INT(63, mapped_value); - - value_to_map = 32; - mapped_value = map_to_pos(value_to_map, max_value_bits); - TEST_ASSERT_EQUAL_INT(63, mapped_value); -} - - -/** - * @test put_n_bits32 - */ - -#define SDP_PB_N 3 - - -static void init_PB32_arrays(uint32_t *z, uint32_t *o) -{ - uint32_t i; - - /* init testarray with all 0 and all 1 */ - for (i = 0; i < SDP_PB_N; i++) { - z[i] = 0; - o[i] = 0xffffffff; - } -} - - -void test_put_n_bits32(void) -{ - uint32_t v, n; - int o, rval; /* return value */ - uint32_t testarray0[SDP_PB_N]; - uint32_t testarray1[SDP_PB_N]; - const uint32_t l = sizeof(testarray0) * CHAR_BIT; - - /* hereafter, the value is v, - * the number of bits to write is n, - * the offset of the bit is o, - * the max length the bitstream in bits is l - */ - - init_PB32_arrays(testarray0, testarray1); - TEST_ASSERT(testarray0[0] == 0); - TEST_ASSERT(testarray0[1] == 0); - TEST_ASSERT(testarray0[2] == 0); - TEST_ASSERT(testarray1[0] == 0xffffffff); - TEST_ASSERT(testarray1[1] == 0xffffffff); - TEST_ASSERT(testarray1[2] == 0xffffffff); - - /*** n=0 ***/ - - /* do not write, left border */ - v = 0; n = 0; o = 0; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(0, rval); - TEST_ASSERT(testarray0[0] == 0); - - rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT_EQUAL_INT(0, rval); - TEST_ASSERT(testarray1[0] == 0xffffffff); - - rval = put_n_bits32(v, n, o, NULL, l); - TEST_ASSERT_EQUAL_INT(0, rval); - - v = 0xffffffff; n = 0; o = 0; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(0, rval); - TEST_ASSERT(testarray0[0] == 0); - - rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT_EQUAL_INT(0, rval); - TEST_ASSERT(testarray1[0] == 0xffffffff); - - rval = put_n_bits32(v, n, o, NULL, l); - TEST_ASSERT_EQUAL_INT(0, rval); - - /* do not write, right border */ - v = 0; n = 0; o = l; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(l, rval); - TEST_ASSERT(testarray0[0] == 0); - - rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT_EQUAL_INT(l, rval); - TEST_ASSERT(testarray1[0] == 0xffffffff); - - rval = put_n_bits32(v, n, o, NULL, l); - TEST_ASSERT_EQUAL_INT(l, rval); - - /* test value = 0xffffffff; N = 0 */ - v = 0xffffffff; n = 0; o = l; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(l, rval); - TEST_ASSERT(testarray0[0] == 0); - - rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT_EQUAL_INT(l, rval); - TEST_ASSERT(testarray1[0] == 0xffffffff); - - rval = put_n_bits32(v, n, o, NULL, l); - TEST_ASSERT_EQUAL_INT(l, rval); - - /*** n=1 ***/ - - /* left border, write 0 */ - v = 0; n = 1; o = 0; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(1, rval); - TEST_ASSERT(testarray0[0] == 0); - - rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT_EQUAL_INT(1, rval); - TEST_ASSERT(testarray1[0] == 0x7fffffff); - - /* left border, write 1 */ - v = 1; n = 1; o = 0; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(1, rval); - TEST_ASSERT(testarray0[0] == 0x80000000); - - rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT_EQUAL_INT(1, rval); - TEST_ASSERT(testarray1[0] == 0xffffffff); - - /* left border, write 32 */ - v = 0xf0f0abcd; n = 32; o = 0; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(rval, 32); - TEST_ASSERT(testarray0[0] == 0xf0f0abcd); - TEST_ASSERT(testarray0[1] == 0); - - rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT_EQUAL_INT(rval, 32); - TEST_ASSERT(testarray1[0] == 0xf0f0abcd); - TEST_ASSERT(testarray1[1] == 0xffffffff); - /* re-init input arrays after clobbering */ - init_PB32_arrays(testarray0, testarray1); - - /* middle, write 2 bits */ - v = 3; n = 2; o = 29; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(rval, 31); - TEST_ASSERT(testarray0[0] == 0x6); - - rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT(testarray1[0] == 0xffffffff); - TEST_ASSERT_EQUAL_INT(rval, 31); - /* re-init input arrays after clobbering */ - init_PB32_arrays(testarray0, testarray1); - - /*** n=5, unsegmented ***/ - - /* left border, write 0 */ - v = 0; n = 5; o = 0; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(rval, 5); - TEST_ASSERT(testarray0[0] == 0); - - rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT(testarray1[0] == 0x07ffffff); - TEST_ASSERT_EQUAL_INT(rval, 5); - /* re-init input arrays after clobbering */ - init_PB32_arrays(testarray0, testarray1); - - /* left border, write 11111 */ - v = 0x1f; n = 5; o = 0; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(rval, 5); - TEST_ASSERT(testarray0[0] == 0xf8000000); - - rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT_EQUAL_INT(rval, 5); - TEST_ASSERT(testarray1[0] == 0xffffffff); - /* re-init input arrays after clobbering */ - init_PB32_arrays(testarray0, testarray1); - - /* middle, write 0 */ - v = 0; n = 5; o = 7; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(rval, 12); - TEST_ASSERT(testarray0[0] == 0); - - rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT_EQUAL_INT(rval, 12); - TEST_ASSERT(testarray1[0] == 0xfe0fffff); - /* re-init input arrays after clobbering */ - init_PB32_arrays(testarray0, testarray1); - - /* middle, write 11111 */ - v = 0x1f; n = 5; o = 7; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(rval, 12); - TEST_ASSERT(testarray0[0] == 0x01f00000); - - rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT_EQUAL_INT(rval, 12); - TEST_ASSERT(testarray1[0] == 0xffffffff); - /* re-init input arrays after clobbering */ - init_PB32_arrays(testarray0, testarray1); - - /* right, write 0 */ - v = 0; n = 5; o = 91; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(rval, 96); - TEST_ASSERT(testarray0[0] == 0); - TEST_ASSERT(testarray0[1] == 0); - TEST_ASSERT(testarray0[0] == 0); - - rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT_EQUAL_INT(rval, 96); - TEST_ASSERT(testarray1[0] == 0xffffffff); - TEST_ASSERT(testarray1[1] == 0xffffffff); - TEST_ASSERT(testarray1[2] == 0xffffffe0); - /* re-init input arrays after clobbering */ - init_PB32_arrays(testarray0, testarray1); - - /* right, write 11111 */ - v = 0x1f; n = 5; o = 91; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(rval, 96); - TEST_ASSERT(testarray0[0] == 0); - TEST_ASSERT(testarray0[1] == 0); - TEST_ASSERT(testarray0[2] == 0x0000001f); - - rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT_EQUAL_INT(rval, 96); - TEST_ASSERT(testarray1[0] == 0xffffffff); - TEST_ASSERT(testarray1[1] == 0xffffffff); - TEST_ASSERT(testarray1[2] == 0xffffffff); - /* re-init input arrays after clobbering */ - init_PB32_arrays(testarray0, testarray1); - - /* 32 bit, write 0 */ - v = 0; n = 32; o = 0; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(rval, 32); - TEST_ASSERT(testarray0[0] == 0x00000000); - - rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT_EQUAL_INT(rval, 32); - TEST_ASSERT(testarray1[0] == 0x00000000); - /* re-init input arrays after clobbering */ - init_PB32_arrays(testarray0, testarray1); - - /* 32 bit, write -1 */ - v = 0xffffffff; n = 32; o = 0; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(rval, 32); - TEST_ASSERT(testarray0[0] == 0xffffffff); - - rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT_EQUAL_INT(rval, 32); - TEST_ASSERT(testarray1[0] == 0xffffffff); - /* re-init input arrays after clobbering */ - init_PB32_arrays(testarray0, testarray1); - - /* SEGMENTED cases */ - /* 5 bit, write 0 */ - v = 0; n = 5; o = 62; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(rval, 67); - TEST_ASSERT(testarray0[0] == 0); - TEST_ASSERT(testarray0[1] == 0); - TEST_ASSERT(testarray0[2] == 0); - - rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT_EQUAL_INT(rval, 67); - TEST_ASSERT(testarray1[0] == 0xffffffff); - TEST_ASSERT(testarray1[1] == 0xfffffffc); - TEST_ASSERT(testarray1[2] == 0x1fffffff); - /* re-init input arrays after clobbering */ - init_PB32_arrays(testarray0, testarray1); - - /* 5 bit, write 1f */ - v = 0x1f; n = 5; o = 62; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(rval, 67); - TEST_ASSERT(testarray0[0] == 0); - TEST_ASSERT(testarray0[1] == 3); - TEST_ASSERT(testarray0[2] == 0xe0000000); - - rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT_EQUAL_INT(rval, 67); - TEST_ASSERT(testarray1[0] == 0xffffffff); - TEST_ASSERT(testarray1[1] == 0xffffffff); - TEST_ASSERT(testarray1[2] == 0xffffffff); - /* re-init input arrays after clobbering */ - init_PB32_arrays(testarray0, testarray1); - - /* 32 bit, write 0 */ - v = 0; n = 32; o = 1; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(rval, 33); - TEST_ASSERT(testarray0[0] == 0x00000000); - TEST_ASSERT(testarray0[1] == 0x00000000); - - rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT_EQUAL_INT(rval, 33); - TEST_ASSERT(testarray1[0] == 0x80000000); - TEST_ASSERT(testarray1[1] == 0x7fffffff); - /* re-init input arrays after clobbering */ - init_PB32_arrays(testarray0, testarray1); - - /* 32 bit, write -1 */ - v = 0xffffffff; n = 32; o = 1; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(rval, 33); - TEST_ASSERT(testarray0[0] == 0x7fffffff); - TEST_ASSERT(testarray0[1] == 0x80000000); - - rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT_EQUAL_INT(rval, 33); - TEST_ASSERT(testarray1[0] == 0xffffffff); - TEST_ASSERT(testarray1[1] == 0xffffffff); - /* re-init input arrays after clobbering */ - init_PB32_arrays(testarray0, testarray1); - - /* test NULL buffer */ - v = 0; n = 0; o = 0; - rval = put_n_bits32(v, n, o, NULL, l); - TEST_ASSERT_EQUAL_INT(rval, 0); - - v = 0; n = 1; o = 0; - rval = put_n_bits32(v, n, o, NULL, l); - TEST_ASSERT_EQUAL_INT(rval, 1); - - v = 0; n = 5; o = 31; - rval = put_n_bits32(v, n, o, NULL, l); - TEST_ASSERT_EQUAL_INT(rval, 36); - - v = 0; n = 2; o = 95; - rval = put_n_bits32(v, n, o, NULL, l); - TEST_ASSERT_EQUAL_INT(rval, 97); /* rval can be longer than l */ - - /* value larger than n allows */ - v = 0x7f; n = 6; o = 10; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(16, rval); - TEST_ASSERT(testarray0[0] == 0x003f0000); - TEST_ASSERT(testarray0[1] == 0); - - rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT_EQUAL_INT(16, rval); - TEST_ASSERT(testarray1[0] == 0xffffffff); - TEST_ASSERT(testarray1[1] == 0xffffffff); - - rval = put_n_bits32(v, n, o, NULL, l); - TEST_ASSERT_EQUAL_INT(16, rval); - /* re-init input arrays after clobbering */ - init_PB32_arrays(testarray0, testarray1); - - v = 0xffffffff; n = 6; o = 10; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(16, rval); - TEST_ASSERT(testarray0[0] == 0x003f0000); - TEST_ASSERT(testarray0[1] == 0); - - rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT_EQUAL_INT(16, rval); - TEST_ASSERT(testarray1[0] == 0xffffffff); - TEST_ASSERT(testarray1[1] == 0xffffffff); - - rval = put_n_bits32(v, n, o, NULL, l); - TEST_ASSERT_EQUAL_INT(16, rval); - /* re-init input arrays after clobbering */ - init_PB32_arrays(testarray0, testarray1); - - /*** error cases ***/ - /* n too large */ - v = 0x0; n = 33; o = 1; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(rval, -1); - TEST_ASSERT(testarray0[0] == 0); - TEST_ASSERT(testarray0[1] == 0); - - rval = put_n_bits32(v, n, o, NULL, l); - TEST_ASSERT_EQUAL_INT(rval, -1); - - /* try to put too much in the bitstream */ - v = 0x1; n = 1; o = 96; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, rval); - TEST_ASSERT(testarray0[0] == 0); - TEST_ASSERT(testarray0[1] == 0); - TEST_ASSERT(testarray0[2] == 0); - - /* this should work (if bitstream=NULL no length check) */ - rval = put_n_bits32(v, n, o, NULL, l); - TEST_ASSERT_EQUAL_INT(97, rval); - - /* offset lager than max_bit_len(l) */ - v = 0x0; n = 32; o = INT32_MAX; - rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, rval); - TEST_ASSERT(testarray1[0] == 0xffffffff); - TEST_ASSERT(testarray1[1] == 0xffffffff); - TEST_ASSERT(testarray1[2] == 0xffffffff); - - rval = put_n_bits32(v, n, o, NULL, l); - TEST_ASSERT(rval < 0); - - /* negative offset */ - v = 0x0; n = 0; o = -1; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(-1, rval); - TEST_ASSERT(testarray0[0] == 0); - TEST_ASSERT(testarray0[1] == 0); - - rval = put_n_bits32(v, n, o, NULL, l); - TEST_ASSERT_EQUAL_INT(-1, rval); - - v = 0x0; n = 0; o = -2; - rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(-1, rval); - TEST_ASSERT(testarray0[0] == 0); - TEST_ASSERT(testarray0[1] == 0); - - rval = put_n_bits32(v, n, o, NULL, l); - TEST_ASSERT_EQUAL_INT(-1, rval); -} - - -/** - * @test Rice_encoder - */ - -void test_Rice_encoder(void) -{ - uint32_t value, g_par, log2_g_par, cw, cw_len; - - /* test minimum Golomb parameter */ - value = 0; log2_g_par = (uint32_t)ilog_2(MIN_ICU_GOLOMB_PAR); g_par = 1U << log2_g_par; cw = ~0U; - cw_len = Rice_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(1, cw_len); - TEST_ASSERT_EQUAL_HEX(0x0, cw); - - value = 31; - cw_len = Rice_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(32, cw_len); - TEST_ASSERT_EQUAL_HEX(0xFFFFFFFE, cw); - - /* test some arbitrary values */ - value = 0; log2_g_par = 4; g_par = 1U << log2_g_par; cw = ~0U; - cw_len = Rice_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(5, cw_len); - TEST_ASSERT_EQUAL_HEX(0x0, cw); - - value = 1; - cw_len = Rice_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(5, cw_len); - TEST_ASSERT_EQUAL_HEX(0x1, cw); - - value = 42; - cw_len = Rice_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(7, cw_len); - TEST_ASSERT_EQUAL_HEX(0x6a, cw); - - value = 446; - cw_len = Rice_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(32, cw_len); - TEST_ASSERT_EQUAL_HEX(0xFFFFFFEE, cw); - - value = 447; - cw_len = Rice_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(32, cw_len); - TEST_ASSERT_EQUAL_HEX(0xFFFFFFEF, cw); - - /* test maximum Golomb parameter for Rice_encoder */ - value = 0; log2_g_par = (uint32_t)ilog_2(MAX_ICU_GOLOMB_PAR); g_par = 1U << log2_g_par; cw = ~0U; - cw_len = Rice_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(32, cw_len); - TEST_ASSERT_EQUAL_HEX(0x0, cw); - - value = 1; - cw_len = Rice_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(32, cw_len); - TEST_ASSERT_EQUAL_HEX(0x1, cw); - - value = 0x7FFFFFFE; - cw_len = Rice_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(32, cw_len); - TEST_ASSERT_EQUAL_HEX(0x7FFFFFFE, cw); - - value = 0x7FFFFFFF; - cw_len = Rice_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(32, cw_len); - TEST_ASSERT_EQUAL_HEX(0x7FFFFFFF, cw); -} - - -/** - * @test Golomb_encoder - */ - -void test_Golomb_encoder(void) -{ - uint32_t value, g_par, log2_g_par, cw, cw_len; - - /* test minimum Golomb parameter */ - value = 0; g_par = MIN_ICU_GOLOMB_PAR; log2_g_par = (uint32_t)ilog_2(g_par); cw = ~0U; - cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(1, cw_len); - TEST_ASSERT_EQUAL_HEX(0x0, cw); - - value = 31; - cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(32, cw_len); - TEST_ASSERT_EQUAL_HEX(0xFFFFFFFE, cw); - - - /* test some arbitrary values with g_par = 16 */ - value = 0; g_par = 16; log2_g_par = (uint32_t)ilog_2(g_par); cw = ~0U; - cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(5, cw_len); - TEST_ASSERT_EQUAL_HEX(0x0, cw); - - value = 1; - cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(5, cw_len); - TEST_ASSERT_EQUAL_HEX(0x1, cw); - - value = 42; - cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(7, cw_len); - TEST_ASSERT_EQUAL_HEX(0x6a, cw); - - value = 446; - cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(32, cw_len); - TEST_ASSERT_EQUAL_HEX(0xFFFFFFEE, cw); - - value = 447; - cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(32, cw_len); - TEST_ASSERT_EQUAL_HEX(0xFFFFFFEF, cw); - - - /* test some arbitrary values with g_par = 3 */ - value = 0; g_par = 3; log2_g_par = (uint32_t)ilog_2(g_par); cw = ~0U; - cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(2, cw_len); - TEST_ASSERT_EQUAL_HEX(0x0, cw); - - value = 1; - cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(3, cw_len); - TEST_ASSERT_EQUAL_HEX(0x2, cw); - - value = 42; - cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(16, cw_len); - TEST_ASSERT_EQUAL_HEX(0xFFFC, cw); - - value = 44; - cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(17, cw_len); - TEST_ASSERT_EQUAL_HEX(0x1FFFB, cw); - - value = 88; - cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(32, cw_len); - TEST_ASSERT_EQUAL_HEX(0xFFFFFFFA, cw); - - value = 89; - cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(32, cw_len); - TEST_ASSERT_EQUAL_HEX(0xFFFFFFFB, cw); - - - /* test maximum Golomb parameter for Golomb_encoder */ - value = 0; g_par = MAX_ICU_GOLOMB_PAR; log2_g_par = (uint32_t)ilog_2(g_par); cw = ~0U; - cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(32, cw_len); - TEST_ASSERT_EQUAL_HEX(0x0, cw); - - value = 1; g_par = MAX_ICU_GOLOMB_PAR; log2_g_par = (uint32_t)ilog_2(g_par); cw = ~0U; - cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(32, cw_len); - TEST_ASSERT_EQUAL_HEX(0x1, cw); - - value = 0x7FFFFFFE; - cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(32, cw_len); - TEST_ASSERT_EQUAL_HEX(0x7FFFFFFE, cw); - - value = 0x7FFFFFFF; - cw_len = Golomb_encoder(value, g_par, log2_g_par, &cw); - TEST_ASSERT_EQUAL_INT(32, cw_len); - TEST_ASSERT_EQUAL_HEX(0x7FFFFFFF, cw); -} - - -/** - * @test encode_value_zero - */ - -void test_encode_value_zero(void) -{ - uint32_t data, model; - int stream_len; - struct encoder_setupt setup = {0}; - uint32_t bitstream[3] = {0}; - - /* setup the setup */ - setup.encoder_par1 = 1; - setup.encoder_par2 = (uint32_t)ilog_2(setup.encoder_par1); - setup.outlier_par = 32; - setup.max_value_bits = 32; - setup.generate_cw = Rice_encoder; - setup.bitstream_adr = bitstream; - setup.max_bit_len = sizeof(bitstream) * CHAR_BIT; - - stream_len = 0; - - data = 0; model = 0; - stream_len = encode_value_zero(data, model, stream_len, &setup); - TEST_ASSERT_EQUAL_INT(2, stream_len); - TEST_ASSERT_EQUAL_HEX(0x80000000, bitstream[0]); - TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[1]); - TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); - - data = 5; model = 0; - stream_len = encode_value_zero(data, model, stream_len, &setup); - TEST_ASSERT_EQUAL_INT(14, stream_len); - TEST_ASSERT_EQUAL_HEX(0xBFF80000, bitstream[0]); - TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[1]); - TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); - - data = 2; model = 7; - stream_len = encode_value_zero(data, model, stream_len, &setup); - TEST_ASSERT_EQUAL_INT(25, stream_len); - TEST_ASSERT_EQUAL_HEX(0xBFFBFF00, bitstream[0]); - TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[1]); - TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); - - /* zero escape mechanism */ - data = 100; model = 42; - /* (100-42)*2+1=117 -> cw 0 + 0x0000_0000_0000_0075 */ - stream_len = encode_value_zero(data, model, stream_len, &setup); - TEST_ASSERT_EQUAL_INT(58, stream_len); - TEST_ASSERT_EQUAL_HEX(0xBFFBFF00, bitstream[0]); - TEST_ASSERT_EQUAL_HEX(0x00001D40, bitstream[1]); - TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); - - /* test overflow */ - data = INT32_MIN; model = 0; - /* (INT32_MIN)*-2-1+1=0(overflow) -> cw 0 + 0x0000_0000_0000_0000 */ - stream_len = encode_value_zero(data, model, stream_len, &setup); - TEST_ASSERT_EQUAL_INT(91, stream_len); - TEST_ASSERT_EQUAL_HEX(0xBFFBFF00, bitstream[0]); - TEST_ASSERT_EQUAL_HEX(0x00001D40, bitstream[1]); - TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); - - /* small buffer error */ - data = 23; model = 26; - stream_len = encode_value_zero(data, model, stream_len, &setup); - TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, stream_len); - - /* reset bitstream to all bits set */ - bitstream[0] = ~0U; - bitstream[1] = ~0U; - bitstream[2] = ~0U; - stream_len = 0; - - /* we use now values with maximum 6 bits */ - setup.max_value_bits = 6; - - /* lowest value before zero encoding */ - data = 53; model = 38; - stream_len = encode_value_zero(data, model, stream_len, &setup); - TEST_ASSERT_EQUAL_INT(32, stream_len); - TEST_ASSERT_EQUAL_HEX(0xFFFFFFFE, bitstream[0]); - TEST_ASSERT_EQUAL_HEX(0xFFFFFFFF, bitstream[1]); - TEST_ASSERT_EQUAL_HEX(0xFFFFFFFF, bitstream[2]); - - /* lowest value with zero encoding */ - data = 0; model = 16; - stream_len = encode_value_zero(data, model, stream_len, &setup); - TEST_ASSERT_EQUAL_INT(39, stream_len); - TEST_ASSERT_EQUAL_HEX(0xFFFFFFFE, bitstream[0]); - TEST_ASSERT_EQUAL_HEX(0x41FFFFFF, bitstream[1]); - TEST_ASSERT_EQUAL_HEX(0xFFFFFFFF, bitstream[2]); - - /* maximum positive value to encode */ - data = 31; model = 0; - stream_len = encode_value_zero(data, model, stream_len, &setup); - TEST_ASSERT_EQUAL_INT(46, stream_len); - TEST_ASSERT_EQUAL_HEX(0xFFFFFFFE, bitstream[0]); - TEST_ASSERT_EQUAL_HEX(0x40FFFFFF, bitstream[1]); - TEST_ASSERT_EQUAL_HEX(0xFFFFFFFF, bitstream[2]); - - /* maximum negative value to encode */ - data = 0; model = 32; - stream_len = encode_value_zero(data, model, stream_len, &setup); - TEST_ASSERT_EQUAL_INT(53, stream_len); - TEST_ASSERT_EQUAL_HEX(0xFFFFFFFE, bitstream[0]); - TEST_ASSERT_EQUAL_HEX(0x40FC07FF, bitstream[1]); - TEST_ASSERT_EQUAL_HEX(0xFFFFFFFF, bitstream[2]); - - /* small buffer error when creating the zero escape symbol*/ - bitstream[0] = 0; - bitstream[1] = 0; - bitstream[2] = 0; - stream_len = 32; - setup.max_bit_len = 32; - data = 31; model = 0; - stream_len = encode_value_zero(data, model, stream_len, &setup); - TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, stream_len); - TEST_ASSERT_EQUAL_HEX(0, bitstream[0]); - TEST_ASSERT_EQUAL_HEX(0, bitstream[1]); - TEST_ASSERT_EQUAL_HEX(0, bitstream[2]); -} - - -/** - * @test encode_value_multi - */ - -void test_encode_value_multi(void) -{ - uint32_t data, model; - int stream_len; - struct encoder_setupt setup = {0}; - uint32_t bitstream[4] = {0}; - - /* setup the setup */ - setup.encoder_par1 = 1; - setup.encoder_par2 = (uint32_t)ilog_2(setup.encoder_par1); - setup.outlier_par = 16; - setup.max_value_bits = 32; - setup.generate_cw = Rice_encoder; - setup.bitstream_adr = bitstream; - setup.max_bit_len = sizeof(bitstream) * CHAR_BIT; - - stream_len = 0; - - data = 0; model = 0; - stream_len = encode_value_multi(data, model, stream_len, &setup); - TEST_ASSERT_EQUAL_INT(1, stream_len); - TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[0]); - TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[1]); - TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); - TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[3]); - - data = 0; model = 1; - stream_len = encode_value_multi(data, model, stream_len, &setup); - TEST_ASSERT_EQUAL_INT(3, stream_len); - TEST_ASSERT_EQUAL_HEX(0x40000000, bitstream[0]); - TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[1]); - TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); - TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[3]); - - data = 1+23; model = 0+23; - stream_len = encode_value_multi(data, model, stream_len, &setup); - TEST_ASSERT_EQUAL_INT(6, stream_len); - TEST_ASSERT_EQUAL_HEX(0x58000000, bitstream[0]); - TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[1]); - TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); - TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[3]); - - /* highest value without multi outlier encoding */ - data = 0+42; model = 8+42; - stream_len = encode_value_multi(data, model, stream_len, &setup); - TEST_ASSERT_EQUAL_INT(22, stream_len); - TEST_ASSERT_EQUAL_HEX(0x5BFFF800, bitstream[0]); - TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[1]); - TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); - TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[3]); - - /* lowest value with multi outlier encoding */ - data = 8+42; model = 0+42; - stream_len = encode_value_multi(data, model, stream_len, &setup); - TEST_ASSERT_EQUAL_INT(41, stream_len); - TEST_ASSERT_EQUAL_HEX(0x5BFFFBFF, bitstream[0]); - TEST_ASSERT_EQUAL_HEX(0xFC000000, bitstream[1]); - TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[2]); - TEST_ASSERT_EQUAL_HEX(0x00000000, bitstream[3]); - - /* highest value with multi outlier encoding */ - data = INT32_MIN; model = 0; - stream_len = encode_value_multi(data, model, stream_len, &setup); - TEST_ASSERT_EQUAL_INT(105, stream_len); - TEST_ASSERT_EQUAL_HEX(0x5BFFFBFF, bitstream[0]); - TEST_ASSERT_EQUAL_HEX(0xFC7FFFFF, bitstream[1]); - TEST_ASSERT_EQUAL_HEX(0xFF7FFFFF, bitstream[2]); - TEST_ASSERT_EQUAL_HEX(0xF7800000, bitstream[3]); - - /* small buffer error */ - data = 0; model = 38; - stream_len = encode_value_multi(data, model, stream_len, &setup); - TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, stream_len); - - /* small buffer error when creating the multi escape symbol*/ - bitstream[0] = 0; - bitstream[1] = 0; - setup.max_bit_len = 32; - - stream_len = 32; - data = 31; model = 0; - stream_len = encode_value_multi(data, model, stream_len, &setup); - TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, stream_len); - TEST_ASSERT_EQUAL_HEX(0, bitstream[0]); - TEST_ASSERT_EQUAL_HEX(0, bitstream[1]); -} diff --git a/test/cmp_icu/test_decmp.c b/test/cmp_icu/test_decmp.c new file mode 100644 index 0000000..523b64f --- /dev/null +++ b/test/cmp_icu/test_decmp.c @@ -0,0 +1,442 @@ +#include <string.h> +#include <stdlib.h> + +#include "unity.h" + +#include "compiler.h" +#include "cmp_debug.h" +#include "cmp_entity.h" +#include "../../lib/cmp_icu.c" /* .c file included to test static functions */ +#include "../../lib/decmp.c" /* .c file included to test static functions */ + + +/* returns the needed size of the compression entry header plus the max size of the + * compressed data if ent == NULL if ent is set the size of the compression + * entry */ +size_t icu_compress_data_entity(struct cmp_entity *ent, const struct cmp_cfg *cfg) +{ + size_t s, hdr_size; + struct cmp_cfg cfg_cpy; + int cmp_size_bits; + + if (!cfg) + return 0; + + if (cfg->icu_output_buf) + debug_print("Warning the set buffer for the compressed data is ignored! The compressed data are write to the compression entry."); + + if (!ent) { + s = cmp_cal_size_of_data(cfg->buffer_length, cfg->data_type); + if (!s) + return 0; + + hdr_size = cmp_ent_cal_hdr_size(cfg->data_type, cfg->cmp_mode == CMP_MODE_RAW); + if (!hdr_size) + return 0; + + return s + hdr_size; + } + + cfg_cpy = *cfg; + cfg_cpy.icu_output_buf = cmp_ent_get_data_buf(ent); + + cmp_size_bits = icu_compress_data(&cfg_cpy); + if (cmp_size_bits < 0) + return 0; + + s = cmp_ent_create(ent, cfg->data_type, cfg->cmp_mode == CMP_MODE_RAW, + cmp_bit_to_4byte(cmp_size_bits)); + if (!s) + return 0; + + if (cmp_ent_write_cmp_pars(ent, cfg, cmp_size_bits)) + return 0; + + return s; +} + + +void test_cmp_decmp_n_imagette_raw(void) +{ + int cmp_size; + size_t s, i; + struct cmp_cfg cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_RAW, 0, CMP_LOSSLESS); + uint16_t data[] = {0, 1, 2, 0x42, INT16_MIN, INT16_MAX, UINT16_MAX}; + uint32_t *compressed_data; + uint16_t *decompressed_data; + struct cmp_entity *ent; + + s = cmp_cfg_icu_buffers(&cfg, data, ARRAY_SIZE(data), NULL, NULL, + NULL, ARRAY_SIZE(data)); + TEST_ASSERT_TRUE(s); + compressed_data = malloc(s); + TEST_ASSERT_TRUE(compressed_data); + s = cmp_cfg_icu_buffers(&cfg, data, ARRAY_SIZE(data), NULL, NULL, + compressed_data, ARRAY_SIZE(data)); + TEST_ASSERT_TRUE(s); + + cmp_size = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(sizeof(data)*CHAR_BIT, cmp_size); + + s = cmp_ent_build(NULL, 0, 0, 0, 0, 0, &cfg, cmp_bit_to_4byte(cmp_size)); + TEST_ASSERT_TRUE(s); + ent = malloc(s); + TEST_ASSERT_TRUE(ent); + s = cmp_ent_build(ent, 0, 0, 0, 0, 0, &cfg, cmp_bit_to_4byte(cmp_size)); + TEST_ASSERT_TRUE(s); + memcpy(cmp_ent_get_data_buf(ent), compressed_data, (cmp_size+7)/8); + + s = decompress_cmp_entiy(ent, NULL, NULL, NULL); + TEST_ASSERT_EQUAL_INT(sizeof(data), s); + decompressed_data = malloc(s); + TEST_ASSERT_TRUE(decompressed_data); + s = decompress_cmp_entiy(ent, NULL, NULL, decompressed_data); + TEST_ASSERT_EQUAL_INT(sizeof(data), s); + + for (i = 0; i < ARRAY_SIZE(data); ++i) { + TEST_ASSERT_EQUAL_INT(data[i], decompressed_data[i]); + + } + + free(compressed_data); + free(ent); + free(decompressed_data); +} + + +/* + * @test re_map_to_pos + */ + +void test_re_map_to_pos(void) +{ + int j; + uint32_t input, result; + unsigned int max_value_bits; + + input = INT32_MIN; + result = re_map_to_pos(map_to_pos(input, 32)); + TEST_ASSERT_EQUAL_INT32(input, result); + + input = INT32_MAX; + result = re_map_to_pos(map_to_pos(input, 32)); + TEST_ASSERT_EQUAL_INT32(input, result); + + input = -1; + result = re_map_to_pos(map_to_pos(input, 32)); + TEST_ASSERT_EQUAL_INT32(input, result); + + input = 0; + result = re_map_to_pos(map_to_pos(input, 32)); + TEST_ASSERT_EQUAL_INT32(input, result); + + input = 1; max_value_bits = 6; + result = re_map_to_pos(map_to_pos(input, max_value_bits)); + TEST_ASSERT_EQUAL_INT32(input, result); + + for (j = -16; j < 15; j++) { + uint32_t map_val = map_to_pos(j, 16) & 0x3F; + result = re_map_to_pos(map_val); + TEST_ASSERT_EQUAL_INT32(j, result); + } + + for (j = INT16_MIN; j < INT16_MAX; j++) { + uint32_t map_val = map_to_pos(j, 16) & 0xFFFF; + result = re_map_to_pos(map_val); + TEST_ASSERT_EQUAL_INT32(j, result); + } +#if 0 + for (j = INT32_MIN; j < INT32_MAX; j++) { + result = re_map_to_pos(map_to_pos(j, 32)); + TEST_ASSERT_EQUAL_INT32(j, result); + } +#endif +} + + +void test_decode_normal(void) +{ + uint32_t decoded_value; + int stream_pos, sample; + /* compressed data from 0 to 6; */ + uint32_t cmp_data[] = {0x5BBDF7E0}; + struct decoder_setup setup = {0}; + + cpu_to_be32s(cmp_data); + + setup.decode_cw_f = rice_decoder; + setup.encoder_par1 = 1; + setup.encoder_par2 = ilog_2(setup.encoder_par1); + setup.bitstream_adr = cmp_data; + setup.max_stream_len = 32; + setup.max_cw_len = 16; + + stream_pos = 0; + for (sample = 0; sample < 7; sample++) { + stream_pos = decode_normal(&decoded_value, stream_pos, &setup); + TEST_ASSERT_EQUAL_HEX(sample, decoded_value); + } + + /* TODO error case: negativ stream_pos */ +} + + +void test_decode_zero(void) +{ + uint32_t decoded_value; + int stream_pos; + uint32_t cmp_data[] = {0x88449FE0}; + struct decoder_setup setup = {0}; + struct cmp_cfg cfg = {0}; + + cpu_to_be32s(cmp_data); + + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.cmp_mode = CMP_MODE_DIFF_ZERO; + cfg.icu_output_buf = cmp_data; + cfg.buffer_length = 4; + + int err = configure_decoder_setup(&setup, 1, 8, CMP_LOSSLESS, 16, &cfg); + TEST_ASSERT_FALSE(err); + + stream_pos = 0; + + stream_pos = decode_zero(&decoded_value, stream_pos, &setup); + TEST_ASSERT_EQUAL_HEX(0, decoded_value); + stream_pos = decode_zero(&decoded_value, stream_pos, &setup); + TEST_ASSERT_EQUAL_HEX(0x4223, decoded_value); + stream_pos = decode_zero(&decoded_value, stream_pos, &setup); + TEST_ASSERT_EQUAL_HEX(7, decoded_value); + TEST_ASSERT_EQUAL_INT(28, stream_pos); + + /* TODO error case: negativ stream_pos */ +} + +void test_decode_multi(void) +{ + uint32_t decoded_value; + int stream_pos; + uint32_t cmp_data[] = {0x16B66DF8, 0x84360000}; + struct decoder_setup setup = {0}; + struct cmp_cfg cfg = {0}; + + cpu_to_be32s(&cmp_data[0]); + cpu_to_be32s(&cmp_data[1]); + + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.cmp_mode = CMP_MODE_DIFF_MULTI; + cfg.icu_output_buf = cmp_data; + cfg.buffer_length = 8; + + int err = configure_decoder_setup(&setup, 3, 8, CMP_LOSSLESS, 16, &cfg); + TEST_ASSERT_FALSE(err); + + stream_pos = 0; + + stream_pos = decode_multi(&decoded_value, stream_pos, &setup); + TEST_ASSERT_EQUAL_HEX(0, decoded_value); + stream_pos = decode_multi(&decoded_value, stream_pos, &setup); + TEST_ASSERT_EQUAL_HEX(1, decoded_value); + stream_pos = decode_multi(&decoded_value, stream_pos, &setup); + TEST_ASSERT_EQUAL_HEX(7, decoded_value); + stream_pos = decode_multi(&decoded_value, stream_pos, &setup); + TEST_ASSERT_EQUAL_HEX(8, decoded_value); + stream_pos = decode_multi(&decoded_value, stream_pos, &setup); + TEST_ASSERT_EQUAL_HEX(9, decoded_value); + stream_pos = decode_multi(&decoded_value, stream_pos, &setup); + TEST_ASSERT_EQUAL_HEX(0x4223, decoded_value); + TEST_ASSERT_EQUAL_INT(47, stream_pos); + +} + + +void test_decompress_imagette_model(void) +{ + uint16_t data[5] = {0}; + uint16_t model[5] = {0, 1, 2, 3, 4}; + uint16_t up_model[5] = {0}; + uint32_t cmp_data[] = {0}; + struct cmp_cfg cfg = {0}; + int stream_pos; + + cmp_data[0] = cpu_to_be32(0x49240000); + + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.cmp_mode = CMP_MODE_MODEL_MULTI; + cfg.input_buf = data; + cfg.model_buf = model; + cfg.icu_new_model_buf = up_model; + cfg.icu_output_buf = cmp_data; + cfg.buffer_length = 4; + cfg.samples = 5; + cfg.model_value = 16; + cfg.golomb_par = 4; + cfg.spill = 48; + + stream_pos = decompress_imagette(&cfg); + TEST_ASSERT_EQUAL_INT(15, stream_pos); + TEST_ASSERT_EQUAL_HEX(1, data[0]); + TEST_ASSERT_EQUAL_HEX(2, data[1]); + TEST_ASSERT_EQUAL_HEX(3, data[2]); + TEST_ASSERT_EQUAL_HEX(4, data[3]); + TEST_ASSERT_EQUAL_HEX(5, data[4]); + + TEST_ASSERT_EQUAL_HEX(0, up_model[0]); + TEST_ASSERT_EQUAL_HEX(1, up_model[1]); + TEST_ASSERT_EQUAL_HEX(2, up_model[2]); + TEST_ASSERT_EQUAL_HEX(3, up_model[3]); + TEST_ASSERT_EQUAL_HEX(4, up_model[4]); +} + +int my_random(unsigned int min, unsigned int max) +{ + if (max-min > RAND_MAX) + TEST_ASSERT(0); + if (min > max) + TEST_ASSERT(0); + return min + rand() / (RAND_MAX / (max - min + 1) + 1); +} + +/* #include <cmp_io.h> */ +/* void test_imagette_1(void) */ +/* { */ +/* size_t i; */ + +/* enum cmp_mode cmp_mode = 1; */ +/* uint16_t model_value = 10; */ +/* struct cmp_cfg cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, cmp_mode, model_value, CMP_LOSSLESS); */ +/* unsigned int samples = 1; */ +/* uint16_t data[1] = {0x8000}; */ +/* uint16_t model[1] = {0}; */ +/* uint16_t up_model[1] = {0}; */ +/* uint16_t de_up_model[1] = {0}; */ +/* uint32_t compressed_data[1]; */ + +/* size_t s = cmp_cfg_buffers(&cfg, data, samples, model, up_model, */ +/* compressed_data, 2*samples); */ +/* TEST_ASSERT(s > 0); */ + +/* uint32_t golomb_par = 9; */ +/* uint32_t spill = 44; */ + +/* cfg.spill = spill; */ +/* cfg.golomb_par = golomb_par; */ +/* /1* print_cfg(&cfg, 0); *1/ */ +/* int cmp_size = icu_compress_data(&cfg, NULL); */ +/* TEST_ASSERT(cmp_size > 0); */ + + +/* s = cmp_ent_build(NULL, 0, 0, 0, 0, 0, &cfg, cmp_size); */ +/* TEST_ASSERT_TRUE(s); */ +/* struct cmp_entity *ent = malloc(s); */ +/* TEST_ASSERT_TRUE(ent); */ +/* s = cmp_ent_build(ent, 0, 0, 0, 0, 0, &cfg, cmp_size); */ +/* TEST_ASSERT_TRUE(s); */ +/* memcpy(cmp_ent_get_data_buf(ent), compressed_data, cmp_bit_to_4byte(cmp_size)); */ + +/* s = decompress_cmp_entiy(ent, model, de_up_model, NULL); */ +/* TEST_ASSERT_EQUAL_INT(samples * sizeof(*data), s); */ +/* uint16_t *decompressed_data = malloc(s); */ +/* TEST_ASSERT_TRUE(decompressed_data); */ +/* s = decompress_cmp_entiy(ent, model, de_up_model, decompressed_data); */ +/* TEST_ASSERT_EQUAL_INT(samples * sizeof(*data), s); */ + +/* for (i = 0; i < samples; ++i) { */ +/* if (data[i] != decompressed_data[i]) */ +/* TEST_ASSERT(0); */ +/* /1* TEST_ASSERT_EQUAL_HEX16(data[i], decompressed_data[i]); *1/ */ +/* /1* TEST_ASSERT_EQUAL_HEX16(up_model[i], de_up_model[i]); *1/ */ +/* } */ + + +/* free(ent); */ +/* free(decompressed_data); */ +/* } */ + +#include <unistd.h> +#include "cmp_io.h" + +void test_imagette_random(void) +{ + unsigned int seed = time(NULL) * getpid(); + size_t i, s, cmp_data_size; + int error; + struct cmp_cfg cfg; + struct cmp_entity *ent; + + enum cmp_mode cmp_mode = my_random(0, 4); + enum cmp_data_type data_type = DATA_TYPE_IMAGETTE; + uint16_t model_value = my_random(0, MAX_MODEL_VALUE); + uint32_t round = my_random(0, 3); + uint32_t samples, compressed_data_len_samples; + uint16_t *data, *model = NULL, *up_model = NULL, *de_up_model = NULL; + + /* Seeds the pseudo-random number generator used by rand() */ + srand(seed); + printf("seed: %u\n", seed); + + /* create random test _data */ + samples = my_random(1, 100000); + s = cmp_cal_size_of_data(samples, data_type); + data = malloc(s); TEST_ASSERT_TRUE(data); + for (i = 0; i < samples; ++i) { + data[i] = my_random(0, UINT16_MAX); + } + if (model_mode_is_used(cmp_mode)) { + model = malloc(s); TEST_ASSERT_TRUE(model); + up_model = malloc(s); TEST_ASSERT_TRUE(up_model); + de_up_model = malloc(s); TEST_ASSERT(de_up_model); + for (i = 0; i < samples; ++i) { + model[i] = my_random(0, UINT16_MAX); + } + } + compressed_data_len_samples = 6*samples; + + /* create a compression configuration */ + cfg = cmp_cfg_icu_create(data_type, cmp_mode, model_value, round); + TEST_ASSERT(cfg.data_type != DATA_TYPE_UNKOWN); + + + cmp_data_size = cmp_cfg_icu_buffers(&cfg, data, samples, model, up_model, + NULL, compressed_data_len_samples); + TEST_ASSERT_TRUE(cmp_data_size); + + uint32_t golomb_par = my_random(MIN_RDCU_GOLOMB_PAR, MAX_RDCU_GOLOMB_PAR); + uint32_t max_spill = get_max_spill(golomb_par, data_type); + TEST_ASSERT(max_spill > 1); + uint32_t spill = my_random(2, max_spill); + + error = cmp_cfg_icu_imagette(&cfg, golomb_par, spill); + TEST_ASSERT_FALSE(error); + + print_cfg(&cfg, 0); + s = icu_compress_data_entity(NULL, &cfg); + TEST_ASSERT_TRUE(s); + ent = malloc(s); TEST_ASSERT_TRUE(ent); + s = icu_compress_data_entity(ent, &cfg); + TEST_ASSERT_TRUE(s); + + s = decompress_cmp_entiy(ent, model, de_up_model, NULL); + TEST_ASSERT_EQUAL_INT(samples * sizeof(*data), s); + uint16_t *decompressed_data = malloc(s); + TEST_ASSERT_TRUE(decompressed_data); + s = decompress_cmp_entiy(ent, model, de_up_model, decompressed_data); + TEST_ASSERT_EQUAL_INT(samples * sizeof(*data), s); + + for (i = 0; i < samples; ++i) { + printf("%u == %u (round: %u)\n", data[i], decompressed_data[i], round); + uint32_t mask = ~0U << round; + if ((data[i]&mask) != (decompressed_data[i]&mask)) + TEST_ASSERT(0); + if (model_mode_is_used(cmp_mode)) + if (up_model[i] != de_up_model[i]) + TEST_ASSERT(0); + } + + free(data); + free(model); + free(up_model); + free(de_up_model); + free(ent); + free(decompressed_data); +} diff --git a/test/cmp_tool/cmp_tool_integration_test.py b/test/cmp_tool/cmp_tool_integration_test.py index 9804d41..aac8b0b 100755 --- a/test/cmp_tool/cmp_tool_integration_test.py +++ b/test/cmp_tool/cmp_tool_integration_test.py @@ -14,6 +14,7 @@ PATH_CMP_TOOL = "./cmp_tool" EXIT_FAILURE = 1 EXIT_SUCCESS = 0 +DATA_TYPE_IMAGETTE = 1 GENERIC_HEADER_SIZE = 32 IMAGETTE_HEADER_SIZE = GENERIC_HEADER_SIZE+4 @@ -409,14 +410,13 @@ def test_compression_diff(): assert(info['cmp_size'] == '20') assert(info['cmp_err'] == '0') else: - # import pdb; pdb.set_trace() header = read_in_cmp_header(f.read()) assert(header['asw_version_id']['value'] == VERSION) assert(header['cmp_ent_size']['value'] == IMAGETTE_HEADER_SIZE+4) assert(header['original_size']['value'] == 10) # todo assert(header['start_time']['value'] < cuc_timestamp(datetime.utcnow())) - #todo + # todo assert(header['end_timestamp']['value'] < cuc_timestamp(datetime.utcnow())) assert(header['data_type']['value'] == 1) assert(header['cmp_mode_used']['value'] == 2) @@ -428,7 +428,7 @@ def test_compression_diff(): assert(header['golomb_par_used']['value'] == 7) assert(header['compressed_data']['value'] == "44444000") - # decompression + # decompression if add_arg == " --no_header": returncode, stdout, stderr = call_cmp_tool( " -i "+output_prefix+".info -d "+output_prefix+".cmp -o "+output_prefix) @@ -441,8 +441,8 @@ def test_compression_diff(): "%s" % ((lambda arg : "Importing decompression information file %s.info ... DONE\n" % (output_prefix) if "--no_header" in arg else "")(add_arg)) + "Importing compressed data file %s.cmp ... DONE\n" % (output_prefix) + - "%s" % ((lambda arg : "Parse the compression entity header ... DONE\n" - if not "--no_header" in arg else "")(add_arg)) + + # "%s" % ((lambda arg : "Parse the compression entity header ... DONE\n" + # if not "--no_header" in arg else "")(add_arg)) + "Decompress data ... DONE\n" + "Write decompressed data to file %s.dat ... DONE\n" % (output_prefix)) @@ -459,7 +459,7 @@ def test_compression_diff(): del_file(output_prefix+'.dat') -def test_model_compression_no_header(): +def test_model_compression(): # generate test data data = '00 01 00 02 00 03 00 04 00 05 \n' model = '00 00 00 01 00 02 00 03 00 04 \n' @@ -481,62 +481,93 @@ def test_model_compression_no_header(): assert(stderr == "") cfg = parse_key_value(stdout) cfg['cmp_mode'] = 'MODE_MODEL_MULTI' + cfg['model_value'] = '16' cfg["samples"] = '5' cfg["buffer_length"] = '2' for key, value in cfg.items(): f.write(key + ' = ' + str(value) + '\n') - # compression - returncode, stdout, stderr = call_cmp_tool( - " -c "+cfg_file_name+" -d "+data_file_name + " -m "+model_file_name+" -o "+output_prefix1+" --no_header") + add_args = [" --no_header", ""] + for add_arg in add_args: + print("Remove this", add_arg) + # compression + returncode, stdout, stderr = call_cmp_tool( + " -c "+cfg_file_name+" -d "+data_file_name + " -m "+model_file_name+" -o "+output_prefix1+ add_arg) - # check compression results - assert(returncode == EXIT_SUCCESS) - assert(stderr == "") - assert(stdout == CMP_START_STR_CMP + - "Importing configuration file %s ... DONE\n" % (cfg_file_name) + - "Importing data file %s ... DONE\n" % (data_file_name) + - "Importing model file %s ... DONE\n" % (model_file_name) + - "Compress data ... DONE\n" + - "Write compressed data to file %s.cmp ... DONE\n" % (output_prefix1) + - "Write decompression information to file %s.info ... DONE\n" % (output_prefix1) + - "Write updated model to file %s_upmodel.dat ... DONE\n" % (output_prefix1)) - # check compressed data - with open(output_prefix1+".cmp", encoding='utf-8') as f: - assert(f.read() == "49 24 00 00 \n") - # check info file - with open(output_prefix1+".info", encoding='utf-8') as f: - info = parse_key_value(f.read()) - assert(info['cmp_mode_used'] == '3') - assert(info['model_value_used'] == cfg['model_value']) - assert(info['round_used'] == cfg['round']) - assert(info['spill_used'] == cfg['spill']) - assert(info['golomb_par_used'] == cfg['golomb_par']) - assert(info['samples_used'] == cfg['samples']) - assert(info['cmp_size'] == '15') - assert(info['cmp_err'] == '0') - - # decompression - returncode, stdout, stderr = call_cmp_tool( - " -i "+output_prefix1+".info -d "+output_prefix1+".cmp -m "+model_file_name+" -o "+output_prefix2) - assert(returncode == EXIT_SUCCESS) - assert(stdout == CMP_START_STR_DECMP + - "Importing decompression information file %s.info ... DONE\n" % (output_prefix1) + - "Importing compressed data file %s.cmp ... DONE\n" % (output_prefix1) + - "Importing model file %s ... DONE\n" % (model_file_name) + - "Decompress data ... DONE\n" + - "Write decompressed data to file %s.dat ... DONE\n" % (output_prefix2) + - "Write updated model to file %s_upmodel.dat ... DONE\n" % (output_prefix2)) - assert(stderr == "") - # check compressed data + # check compression results + assert(stderr == "") + assert(stdout == CMP_START_STR_CMP + + "Importing configuration file %s ... DONE\n" % (cfg_file_name) + + "Importing data file %s ... DONE\n" % (data_file_name) + + "Importing model file %s ... DONE\n" % (model_file_name) + + "Compress data ... DONE\n" + + "Write compressed data to file %s.cmp ... DONE\n" % (output_prefix1) + + "%s" % ((lambda arg : "Write decompression information to file %s.info ... DONE\n" % (output_prefix1) + if arg == " --no_header" else "")(add_arg)) + + "Write updated model to file %s_upmodel.dat ... DONE\n" % (output_prefix1) + ) + assert(returncode == EXIT_SUCCESS) + + if add_arg == " --no_header": + # check compressed data + with open(output_prefix1+".cmp", encoding='utf-8') as f: + assert(f.read() == "49 24 00 00 \n") + # check info file + with open(output_prefix1+".info", encoding='utf-8') as f: + info = parse_key_value(f.read()) + assert(info['cmp_mode_used'] == '3') + assert(info['model_value_used'] == cfg['model_value']) + assert(info['round_used'] == cfg['round']) + assert(info['spill_used'] == cfg['spill']) + assert(info['golomb_par_used'] == cfg['golomb_par']) + assert(info['samples_used'] == cfg['samples']) + assert(info['cmp_size'] == '15') + assert(info['cmp_err'] == '0') + else: + with open(output_prefix1+".cmp", encoding='utf-8') as f: + header = read_in_cmp_header(f.read()) + assert(header['asw_version_id']['value'] == VERSION) + assert(header['cmp_ent_size']['value'] == IMAGETTE_HEADER_SIZE+4) + assert(header['original_size']['value'] == 10) + # todo + assert(header['start_time']['value'] < cuc_timestamp(datetime.utcnow())) + #todo + assert(header['end_timestamp']['value'] < cuc_timestamp(datetime.utcnow())) + assert(header['data_type']['value'] == DATA_TYPE_IMAGETTE) + assert(header['cmp_mode_used']['value'] == 3) + assert(header['model_value_used']['value'] == int(cfg['model_value'])) + assert(header['model_id']['value'] == 53264) + assert(header['model_counter']['value'] == 1) + assert(header['lossy_cmp_par_used']['value'] == int(cfg['round'])) + assert(header['spill_used']['value'] == int(cfg['spill'])) + assert(header['golomb_par_used']['value'] == int(cfg['golomb_par'])) + assert(header['compressed_data']['value'] == "49240000") + + # decompression + if add_arg == " --no_header": + returncode, stdout, stderr = call_cmp_tool( + " -i "+output_prefix1+".info -d "+output_prefix1+".cmp -m "+model_file_name+" -o "+output_prefix2) + else: + returncode, stdout, stderr = call_cmp_tool("-d "+output_prefix1+".cmp -m "+model_file_name+" -o "+output_prefix2) + assert(stderr == "") + assert(stdout == CMP_START_STR_DECMP + + "%s" % ((lambda arg : "Importing decompression information file %s.info ... DONE\n" % (output_prefix1) + if "--no_header" in arg else "")(add_arg)) + + "Importing compressed data file %s.cmp ... DONE\n" % (output_prefix1) + + "Importing model file %s ... DONE\n" % (model_file_name) + + "Decompress data ... DONE\n" + + "Write decompressed data to file %s.dat ... DONE\n" % (output_prefix2) + + "Write updated model to file %s_upmodel.dat ... DONE\n" % (output_prefix2)) + assert(returncode == EXIT_SUCCESS) + # check compressed data - with open(output_prefix2+".dat", encoding='utf-8') as f: - assert(f.read() == data) + with open(output_prefix2+".dat", encoding='utf-8') as f: + assert(f.read() == data) - with open(output_prefix1+"_upmodel.dat", encoding='utf-8') as f1: - with open(output_prefix2+"_upmodel.dat", encoding='utf-8') as f2: - assert(f1.read() == f2.read() == - '00 00 00 01 00 02 00 03 00 04 \n') + with open(output_prefix1+"_upmodel.dat", encoding='utf-8') as f1: + with open(output_prefix2+"_upmodel.dat", encoding='utf-8') as f2: + assert(f1.read() == f2.read() == + '00 00 00 01 00 02 00 03 00 04 \n') # clean up finally: del_file(data_file_name) @@ -582,7 +613,7 @@ def test_raw_mode_compression(): with open(output_prefix+".cmp", encoding='utf-8') as f: if "--no_header" in arg: # check compressed data file - assert(f.read() == data[:-1]+"00 00 \n") + assert(f.read() == data)#[:-1]+"00 00 \n") # check info file with open(output_prefix+".info", encoding='utf-8') as f: info = parse_key_value(f.read()) @@ -627,8 +658,6 @@ def test_raw_mode_compression(): "%s" % ((lambda arg : "Importing decompression information file %s.info ... DONE\n" % (output_prefix) if "--no_header" in arg else "")(arg)) + "Importing compressed data file %s.cmp ... DONE\n" % (output_prefix) + - "%s" % ((lambda arg : "Parse the compression entity header ... DONE\n" - if not "--no_header" in arg else "")(arg)) + "Decompress data ... DONE\n" + "Write decompressed data to file %s.dat ... DONE\n" % (output_prefix)) assert(returncode == EXIT_SUCCESS) @@ -979,11 +1008,9 @@ def test_sample_used_is_to_big(): else: assert(stdout == CMP_START_STR_DECMP + "Importing compressed data file %s ... DONE\n" % (cmp_file_name) + - "Parse the compression entity header ... DONE\n" + "Decompress data ... FAILED\n") - assert(stderr == "Error: Buffer overflow detected.\n" + - "Error: Compressed values could not be decoded.\n") + assert(stderr == "Error: Buffer overflow detected.\n") assert(returncode == EXIT_FAILURE) finally: @@ -1009,6 +1036,7 @@ def test_header_wrong_formatted(): finally: del_file(cmp_file_name) + def test_header_read_in(): cmp_file_name = 'test_header_read_in.cmp' @@ -1081,7 +1109,7 @@ def test_header_read_in(): assert(returncode == EXIT_FAILURE) assert(stdout == CMP_START_STR_DECMP + "Importing compressed data file %s ... DONE\n" % (cmp_file_name) + - "Parse the compression entity header ... FAILED\n" ) + "Decompress data ... FAILED\n" ) assert(stderr == "Error: Compression mode not supported.\n") finally: @@ -1106,25 +1134,27 @@ def test_model_fiel_erros(): f.write(cfg) # no -m option in model mode - returncode, stdout, stderr = call_cmp_tool( - " -c "+cfg_file_name+" -d "+data_file_name + " -o "+output_prefix+" --no_header") - assert(returncode == EXIT_FAILURE) - assert(stdout == CMP_START_STR_CMP + - "Importing configuration file %s ... DONE\n" % (cfg_file_name) + - "Importing data file %s ... DONE\n" % (data_file_name) + - "Importing model file ... FAILED\n" ) - assert(stderr == "cmp_tool: No model file (-m option) specified.\n") + add_args = [" --no_header", ""] + for add_arg in add_args: + returncode, stdout, stderr = call_cmp_tool( + " -c "+cfg_file_name+" -d "+data_file_name + " -o "+output_prefix+add_arg) + assert(returncode == EXIT_FAILURE) + assert(stdout == CMP_START_STR_CMP + + "Importing configuration file %s ... DONE\n" % (cfg_file_name) + + "Importing data file %s ... DONE\n" % (data_file_name) + + "Importing model file ... FAILED\n" ) + assert(stderr == "cmp_tool: No model file (-m option) specified.\n") - # model file to small - with open(model_file_name, 'w', encoding='utf-8') as f: - f.write(model) - returncode, stdout, stderr = call_cmp_tool( - " -c "+cfg_file_name+" -d "+data_file_name + " -m "+model_file_name+" -o "+output_prefix) - assert(returncode == EXIT_FAILURE) - assert(stdout == CMP_START_STR_CMP + - "Importing configuration file %s ... DONE\n" % (cfg_file_name) + - "Importing data file %s ... DONE\n" % (data_file_name) + - "Importing model file %s ... FAILED\n" % (model_file_name) ) + # model file to small + with open(model_file_name, 'w', encoding='utf-8') as f: + f.write(model) + returncode, stdout, stderr = call_cmp_tool( + " -c "+cfg_file_name+" -d "+data_file_name + " -m "+model_file_name+" -o "+output_prefix) + assert(returncode == EXIT_FAILURE) + assert(stdout == CMP_START_STR_CMP + + "Importing configuration file %s ... DONE\n" % (cfg_file_name) + + "Importing data file %s ... DONE\n" % (data_file_name) + + "Importing model file %s ... FAILED\n" % (model_file_name) ) assert(stderr == "cmp_tool: %s: Error: The files do not contain enough data as requested.\n" % (model_file_name)) # updated model can not write diff --git a/test/meson.build b/test/meson.build index b843e00..4743653 100644 --- a/test/meson.build +++ b/test/meson.build @@ -34,10 +34,6 @@ subdir('tools') subdir('cmp_tool') -cunit_dep = dependency('cunit', required : false) - unity_dep = dependency('unity', fallback : ['unity', 'unity_dep']) -# unity_proj = subproject('unity') -# unity_dep = unity_proj.get_variable('unity_dep') subdir('cmp_icu') -- GitLab From b2e77e60a76c5a24b932f540881043929d3e475d Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Tue, 31 May 2022 15:32:34 +0200 Subject: [PATCH 21/46] bug fixing --- include/cmp_data_types.h | 2 +- lib/cmp_rdcu.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/cmp_data_types.h b/include/cmp_data_types.h index 75b69be..02bb840 100644 --- a/include/cmp_data_types.h +++ b/include/cmp_data_types.h @@ -270,7 +270,7 @@ size_t size_of_a_sample(enum cmp_data_type data_type); unsigned int cmp_cal_size_of_data(unsigned int samples, enum cmp_data_type data_type); int cmp_input_size_to_samples(unsigned int size, enum cmp_data_type data_type); -int cmp_input_big_to_cpu_endianness(void *data, u_int32_t data_size_byte, +int cmp_input_big_to_cpu_endianness(void *data, uint32_t data_size_byte, enum cmp_data_type data_type); #endif /* CMP_DATA_TYPE_H */ diff --git a/lib/cmp_rdcu.c b/lib/cmp_rdcu.c index 34606f4..2a8f214 100644 --- a/lib/cmp_rdcu.c +++ b/lib/cmp_rdcu.c @@ -141,7 +141,7 @@ static int rdcu_cfg_gen_par_is_invalid(const struct cmp_cfg *cfg) * on error the data_type record is set to DATA_TYPE_UNKOWN */ -struct cmp_cfg rdcu_cfg_create(enum cmp_ent_data_type data_type, enum cmp_mode cmp_mode, +struct cmp_cfg rdcu_cfg_create(enum cmp_data_type data_type, enum cmp_mode cmp_mode, uint32_t model_value, uint32_t lossy_par) { struct cmp_cfg cfg = {0}; -- GitLab From 59ee1a0c21f212fab55517f397be1f47ba721dfe Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Wed, 8 Jun 2022 15:34:32 +0200 Subject: [PATCH 22/46] Incorporate the update from the ICU integration --- include/cmp_data_types.h | 9 +++- include/cmp_entity.h | 10 +++-- include/cmp_guess.h | 3 +- include/cmp_icu.h | 2 +- include/cmp_io.h | 4 +- include/cmp_rdcu.h | 2 +- include/cmp_rdcu_extended.h | 2 +- include/cmp_support.h | 2 +- include/decmp.h | 8 +--- include/rdcu_ctrl.h | 9 ++-- include/rdcu_pkt_to_file.h | 2 +- include/rmap.h | 9 ++-- lib/cmp_data_types.c | 6 +-- lib/cmp_entity.c | 10 ++--- lib/cmp_guess.c | 1 + lib/cmp_icu.c | 14 ++++--- lib/cmp_io.c | 13 +++--- lib/cmp_rdcu.c | 24 ++++++----- lib/cmp_support.c | 10 +++-- lib/rdcu_ctrl.c | 83 ++++++++++++++++++++++++++++++------- lib/rdcu_pkt_to_file.c | 12 +++--- lib/rdcu_rmap.c | 34 ++++++--------- lib/rmap.c | 13 +++--- 23 files changed, 168 insertions(+), 114 deletions(-) diff --git a/include/cmp_data_types.h b/include/cmp_data_types.h index 02bb840..9c8a470 100644 --- a/include/cmp_data_types.h +++ b/include/cmp_data_types.h @@ -36,8 +36,8 @@ #include <stdint.h> -#include "compiler.h" -#include "cmp_support.h" +#include <compiler.h> +#include <cmp_support.h> #define MAX_USED_NC_IMAGETTE_BITS 16 #define MAX_USED_SATURATED_IMAGETTE_BITS 16 /* TBC */ @@ -135,6 +135,7 @@ extern struct cmp_max_used_bits max_used_bits; #define MULTI_ENTRY_HDR_SIZE 12 compile_time_assert(MULTI_ENTRY_HDR_SIZE % sizeof(uint32_t) == 0, N_DPU_ICU_MULTI_ENTRY_HDR_NOT_4_BYTE_ALLIED); +__extension__ struct multi_entry_hdr { uint32_t timestamp_coarse; uint16_t timestamp_fine; @@ -206,6 +207,7 @@ struct f_fx_efx_ncob_ecob { } __attribute__((packed)); +__extension__ struct l_fx { uint32_t exp_flags:24; /* selected exposure flags (24 flags) */ uint32_t fx; @@ -213,6 +215,7 @@ struct l_fx { } __attribute__((packed)); +__extension__ struct l_fx_efx { uint32_t exp_flags:24; /* selected exposure flags (24 flags) */ uint32_t fx; @@ -221,6 +224,7 @@ struct l_fx_efx { } __attribute__((packed)); +__extension__ struct l_fx_ncob { uint32_t exp_flags:24; /* selected exposure flags (24 flags) */ uint32_t fx; @@ -232,6 +236,7 @@ struct l_fx_ncob { } __attribute__((packed)); +__extension__ struct l_fx_efx_ncob_ecob { uint32_t exp_flags:24; /* selected exposure flags (24 flags) */ uint32_t fx; diff --git a/include/cmp_entity.h b/include/cmp_entity.h index a06715f..0c9a606 100644 --- a/include/cmp_entity.h +++ b/include/cmp_entity.h @@ -29,8 +29,8 @@ #include <stdint.h> -#include "compiler.h" -#include "cmp_support.h" +#include <compiler.h> +#include <cmp_support.h> #define GENERIC_HEADER_SIZE 32 @@ -285,11 +285,13 @@ ssize_t cmp_ent_get_cmp_data(struct cmp_entity *ent, uint32_t *data_buf, uint32_t cmp_ent_cal_hdr_size(enum cmp_data_type data_type, int raw_mode); -#if __has_include(<time.h>) -#include <time.h> +#if defined __has_include +# if __has_include(<time.h>) +# include <time.h> /* create a timestamp for the compression header */ extern const struct tm EPOCH_DATE; uint64_t cmp_ent_create_timestamp(const struct timespec *ts); +# endif #endif /* print and parse functions */ diff --git a/include/cmp_guess.h b/include/cmp_guess.h index 9d3ac42..4b26e9c 100644 --- a/include/cmp_guess.h +++ b/include/cmp_guess.h @@ -20,8 +20,7 @@ #ifndef CMP_GUESS_H #define CMP_GUESS_H -#include "cmp_support.h" - +#include <cmp_support.h> #define DEFAULT_GUESS_LEVEL 2 diff --git a/include/cmp_icu.h b/include/cmp_icu.h index 81a1160..01e528c 100644 --- a/include/cmp_icu.h +++ b/include/cmp_icu.h @@ -20,7 +20,7 @@ #ifndef _CMP_ICU_H_ #define _CMP_ICU_H_ -#include "cmp_support.h" +#include <cmp_support.h> /* return code if the bitstream buffer is too small to store the whole bitstream */ diff --git a/include/cmp_io.h b/include/cmp_io.h index f807825..6de2222 100644 --- a/include/cmp_io.h +++ b/include/cmp_io.h @@ -17,8 +17,8 @@ #include <string.h> -#include "cmp_support.h" -#include "cmp_entity.h" +#include <cmp_support.h> +#include <cmp_entity.h> #define MAX_CONFIG_LINE 256 diff --git a/include/cmp_rdcu.h b/include/cmp_rdcu.h index 14e0af9..6273549 100644 --- a/include/cmp_rdcu.h +++ b/include/cmp_rdcu.h @@ -21,7 +21,7 @@ #ifndef _CMP_RDCU_H_ #define _CMP_RDCU_H_ -#include "cmp_support.h" +#include <cmp_support.h> /* Compression Error Register bits definition, see RDCU-FRS-FN-0952 */ diff --git a/include/cmp_rdcu_extended.h b/include/cmp_rdcu_extended.h index a4f33e0..7ab229b 100644 --- a/include/cmp_rdcu_extended.h +++ b/include/cmp_rdcu_extended.h @@ -18,7 +18,7 @@ #ifndef _CMP_RDCU_EXTENDED_H_ #define _CMP_RDCU_EXTENDED_H_ -#include "../include/cmp_rdcu.h" +#include <cmp_rdcu.h> int rdcu_start_compression(void); diff --git a/include/cmp_support.h b/include/cmp_support.h index 850f0a7..f5e0b38 100644 --- a/include/cmp_support.h +++ b/include/cmp_support.h @@ -109,7 +109,7 @@ enum cmp_data_type { DATA_TYPE_F_CAM_IMAGETTE, DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE, DATA_TYPE_F_CAM_OFFSET, - DATA_TYPE_F_CAM_BACKGROUND, + DATA_TYPE_F_CAM_BACKGROUND }; diff --git a/include/decmp.h b/include/decmp.h index 2f8478c..4046798 100644 --- a/include/decmp.h +++ b/include/decmp.h @@ -19,10 +19,8 @@ #ifndef DECMP_H_ #define DECMP_H_ -#include "cmp_entity.h" -#include "cmp_support.h" - -void *malloc_decompressed_data(const struct cmp_info *info); +#include <cmp_entity.h> +#include <cmp_support.h> int decompress_data(uint32_t *compressed_data, void *de_model_buf, const struct cmp_info *info, void *decompressed_data); @@ -30,6 +28,4 @@ int decompress_data(uint32_t *compressed_data, void *de_model_buf, int decompress_cmp_entiy(struct cmp_entity *ent, void *model_buf, void *up_model_buf, void *decompressed_data); -double get_compression_ratio(const struct cmp_info *info); - #endif /* DECMP_H_ */ diff --git a/include/rdcu_ctrl.h b/include/rdcu_ctrl.h index ec5b9a2..ac3f03f 100644 --- a/include/rdcu_ctrl.h +++ b/include/rdcu_ctrl.h @@ -245,11 +245,14 @@ uint32_t rdcu_get_golomb_param(void); uint32_t rdcu_get_compr_data_start_addr(void); -uint32_t rdcu_get_compr_data_size(void); +uint32_t rdcu_get_compr_data_size_bit(void); +uint32_t rdcu_get_compr_data_size_byte(void); -uint32_t rdcu_get_compr_data_adaptive_1_size(void); +uint32_t rdcu_get_compr_data_adaptive_1_size_bit(void); +uint32_t rdcu_get_compr_data_adaptive_1_size_byte(void); -uint32_t rdcu_get_compr_data_adaptive_2_size(void); +uint32_t rdcu_get_compr_data_adaptive_2_size_bit(void); +uint32_t rdcu_get_compr_data_adaptive_2_size_byte(void); uint16_t rdcu_get_compr_error(void); diff --git a/include/rdcu_pkt_to_file.h b/include/rdcu_pkt_to_file.h index 87c3d4b..1c350c8 100644 --- a/include/rdcu_pkt_to_file.h +++ b/include/rdcu_pkt_to_file.h @@ -18,7 +18,7 @@ #ifndef _RDCU_PKT_TO_FILE_H_ #define _RDCU_PKT_TO_FILE_H_ -#include "cmp_support.h" +#include <cmp_support.h> /* directory where the tc files are stored, when --rdcu_pkt option is set */ #define TC_DIR "TC_FILES" diff --git a/include/rmap.h b/include/rmap.h index 5156679..c581575 100644 --- a/include/rmap.h +++ b/include/rmap.h @@ -20,6 +20,7 @@ #define RMAP_H #include <stdint.h> +#include <compiler.h> /** * valid RMAP command codes, see Table 5-1 of ECSS‐E‐ST‐50‐52C @@ -149,11 +150,9 @@ struct rmap_instruction { #else #error "Unknown byte order" #endif -}__attribute__((packed)); -#if 0 -compile_time_assert((sizeof(struct rmap_instruction) == sizeof(uint8_t), - RMAP_INSTRUCTION_STRUCT_WRONG_SIZE)); -#endif +} __attribute__((packed)); + +compile_time_assert(sizeof(struct rmap_instruction) == sizeof(uint8_t), RMAP_INSTRUCTION_STRUCT_WRONG_SIZE); /** diff --git a/lib/cmp_data_types.c b/lib/cmp_data_types.c index 749964d..8dd5792 100644 --- a/lib/cmp_data_types.c +++ b/lib/cmp_data_types.c @@ -17,9 +17,9 @@ */ -#include "cmp_data_types.h" -#include "cmp_debug.h" -#include "byteorder.h" +#include <cmp_data_types.h> +#include <cmp_debug.h> +#include <byteorder.h> /* the maximum length of the different data products types in bits */ diff --git a/lib/cmp_entity.c b/lib/cmp_entity.c index d058b17..0296894 100644 --- a/lib/cmp_entity.c +++ b/lib/cmp_entity.c @@ -30,11 +30,11 @@ # endif #endif -#include "byteorder.h" -#include "cmp_debug.h" -#include "cmp_support.h" -#include "cmp_data_types.h" -#include "cmp_entity.h" +#include <byteorder.h> +#include <cmp_debug.h> +#include <cmp_support.h> +#include <cmp_data_types.h> +#include <cmp_entity.h> #ifdef HAS_TIME_H diff --git a/lib/cmp_guess.c b/lib/cmp_guess.c index c173814..3107aaf 100644 --- a/lib/cmp_guess.c +++ b/lib/cmp_guess.c @@ -15,6 +15,7 @@ * * @brief helps the user to find a good compression parameters for a given * dataset + * @warning this part of the software is not intended to run on-board on the ICU. */ #include <limits.h> diff --git a/lib/cmp_icu.c b/lib/cmp_icu.c index 057ee64..2d11006 100644 --- a/lib/cmp_icu.c +++ b/lib/cmp_icu.c @@ -32,11 +32,11 @@ #include <string.h> #include <limits.h> -#include "byteorder.h" -#include "cmp_debug.h" -#include "cmp_data_types.h" -#include "cmp_support.h" -#include "cmp_icu.h" +#include <byteorder.h> +#include <cmp_debug.h> +#include <cmp_data_types.h> +#include <cmp_support.h> +#include <cmp_icu.h> /* pointer to a code word generation function */ @@ -75,7 +75,9 @@ struct cmp_cfg cmp_cfg_icu_create(enum cmp_data_type data_type, enum cmp_mode cm uint32_t model_value, uint32_t lossy_par) { int cfg_valid; - struct cmp_cfg cfg = {0}; + struct cmp_cfg cfg; + + memset(&cfg, 0, sizeof(cfg)); cfg.data_type = data_type; cfg.cmp_mode = cmp_mode; diff --git a/lib/cmp_io.c b/lib/cmp_io.c index 99e80c6..0e74c0d 100644 --- a/lib/cmp_io.c +++ b/lib/cmp_io.c @@ -15,6 +15,7 @@ * more details. * * @brief compression tool Input/Output library + * @warning this part of the software is not intended to run on-board on the ICU. */ #include <stdio.h> @@ -24,12 +25,12 @@ #include <ctype.h> #include <sys/stat.h> -#include "cmp_tool-config.h" -#include "cmp_io.h" -#include "cmp_support.h" -#include "rdcu_cmd.h" -#include "byteorder.h" -#include "cmp_data_types.h" +#include <cmp_tool-config.h> +#include <cmp_io.h> +#include <cmp_support.h> +#include <rdcu_cmd.h> +#include <byteorder.h> +#include <cmp_data_types.h> /* directory to convert from data_type to string */ diff --git a/lib/cmp_rdcu.c b/lib/cmp_rdcu.c index 2a8f214..336fc87 100644 --- a/lib/cmp_rdcu.c +++ b/lib/cmp_rdcu.c @@ -28,13 +28,14 @@ #include <stdint.h> #include <stdio.h> +#include <string.h> -#include "rdcu_cmd.h" -#include "cmp_support.h" -#include "cmp_data_types.h" -#include "rdcu_ctrl.h" -#include "rdcu_rmap.h" -#include "cmp_debug.h" +#include <rdcu_cmd.h> +#include <cmp_debug.h> +#include <cmp_support.h> +#include <cmp_data_types.h> +#include <rdcu_ctrl.h> +#include <rdcu_rmap.h> #define IMA_SAM2BYT \ @@ -144,7 +145,9 @@ static int rdcu_cfg_gen_par_is_invalid(const struct cmp_cfg *cfg) struct cmp_cfg rdcu_cfg_create(enum cmp_data_type data_type, enum cmp_mode cmp_mode, uint32_t model_value, uint32_t lossy_par) { - struct cmp_cfg cfg = {0}; + struct cmp_cfg cfg; + + memset(&cfg, 0, sizeof(cfg)); cfg.data_type = data_type; cfg.cmp_mode = cmp_mode; @@ -157,6 +160,7 @@ struct cmp_cfg rdcu_cfg_create(enum cmp_data_type data_type, enum cmp_mode cmp_m return cfg; } + /** * @brief check if a buffer is in inside the RDCU SRAM * @@ -801,9 +805,9 @@ int rdcu_read_cmp_info(struct cmp_info *info) info->rdcu_new_model_adr_used = rdcu_get_new_model_addr_used(); info->samples_used = rdcu_get_samples_used(); info->rdcu_cmp_adr_used = rdcu_get_compr_data_start_addr(); - info->cmp_size = rdcu_get_compr_data_size(); - info->ap1_cmp_size = rdcu_get_compr_data_adaptive_1_size(); - info->ap2_cmp_size = rdcu_get_compr_data_adaptive_2_size(); + info->cmp_size = rdcu_get_compr_data_size_bit(); + info->ap1_cmp_size = rdcu_get_compr_data_adaptive_1_size_bit(); + info->ap2_cmp_size = rdcu_get_compr_data_adaptive_2_size_bit(); info->cmp_err = rdcu_get_compr_error(); } return 0; diff --git a/lib/cmp_support.c b/lib/cmp_support.c index a02e897..666ce96 100644 --- a/lib/cmp_support.c +++ b/lib/cmp_support.c @@ -18,8 +18,8 @@ */ -#include "cmp_support.h" -#include "cmp_debug.h" +#include <cmp_support.h> +#include <cmp_debug.h> /** @@ -378,13 +378,15 @@ unsigned int cmp_up_model(unsigned int data, unsigned int model, unsigned int model_value, unsigned int round) { + uint64_t weighted_model, weighted_data; + /* round and round back input because for decompression the accurate * data values are not available */ data = round_inv(round_fwd(data, round), round); /* cast uint64_t to prevent overflow in the multiplication */ - uint64_t weighted_model = (uint64_t)model * model_value; - uint64_t weighted_data = (uint64_t)data * (MAX_MODEL_VALUE - model_value); + weighted_model = (uint64_t)model * model_value; + weighted_data = (uint64_t)data * (MAX_MODEL_VALUE - model_value); /* truncation is intended */ return (unsigned int)((weighted_model + weighted_data) / MAX_MODEL_VALUE); } diff --git a/lib/rdcu_ctrl.c b/lib/rdcu_ctrl.c index a1e3b0f..6f743c6 100644 --- a/lib/rdcu_ctrl.c +++ b/lib/rdcu_ctrl.c @@ -1305,44 +1305,97 @@ uint32_t rdcu_get_compr_data_start_addr(void) /** - * @brief get compressed data size + * @brief get the need bytes for the given bits + * + * @param cmp_size_bit compressed data size, measured in bits + * + * @returns the size in bytes to store the compressed data + */ + +static uint32_t rdcu_bit_to_byte(unsigned int cmp_size_bit) +{ + return ((cmp_size_bit + 7) / 8); +} + + +/** + * @brief get compressed data size in bits * @see RDCU-FRS-FN-0922 * * @returns the compressed data size in bits */ -uint32_t rdcu_get_compr_data_size(void) +uint32_t rdcu_get_compr_data_size_bit(void) { return rdcu->compr_data_size; } /** - * @brief get compressed data adaptive 1 size + * @brief get compressed data size in bytes + * @see RDCU-FRS-FN-0922 + * + * @returns the compressed data size in bytes + */ + +uint32_t rdcu_get_compr_data_size_byte(void) +{ + return rdcu_bit_to_byte(rdcu_get_compr_data_size_bit()); +} + + +/** + * @brief get compressed data adaptive 1 size in bits * @see RDCU-FRS-FN-0932 * * @returns the adaptive 1 compressed data size in bits */ -uint32_t rdcu_get_compr_data_adaptive_1_size(void) +uint32_t rdcu_get_compr_data_adaptive_1_size_bit(void) { return rdcu->compr_data_adaptive_1_size; } /** - * @brief get compressed data adaptive 2 size + * @brief get compressed data adaptive 1 size in bytes + * @see RDCU-FRS-FN-0932 + * + * @returns the adaptive 1 compressed data size in bytes + */ + +uint32_t rdcu_get_compr_data_adaptive_1_size_byte(void) +{ + return rdcu_bit_to_byte(rdcu_get_compr_data_adaptive_1_size_bit()); +} + + +/** + * @brief get compressed data adaptive 2 size in bits * @see RDCU-FRS-FN-0942 * * @returns the adaptive 2 compressed data size in bits */ -uint32_t rdcu_get_compr_data_adaptive_2_size(void) +uint32_t rdcu_get_compr_data_adaptive_2_size_bit(void) { return rdcu->compr_data_adaptive_2_size; } +/** + * @brief get compressed data adaptive 2 size in bytes + * @see RDCU-FRS-FN-0942 + * + * @returns the adaptive 2 compressed data size in bytes + */ + +uint32_t rdcu_get_compr_data_adaptive_2_size_byte(void) +{ + return rdcu_bit_to_byte(rdcu_get_compr_data_adaptive_2_size_bit()); +} + + /** * @brief get compression error code * @see RDCU-FRS-FN-0954 @@ -1606,7 +1659,7 @@ int rdcu_write_sram_16(uint16_t *buf, uint32_t addr, uint32_t size) { uint32_t i; - for (i = 0; i < size/sizeof(uint16_t); i++){ + for (i = 0; i < size/sizeof(uint16_t); i++) { uint16_t *sram_buf = (uint16_t *)&rdcu->sram[addr]; sram_buf[i] = cpu_to_be16(buf[i]); @@ -1652,9 +1705,9 @@ int rdcu_write_sram_32(uint32_t *buf, uint32_t addr, uint32_t size) { uint32_t i; - for (i = 0; i < size/sizeof(uint32_t); i++){ - uint32_t *sram_buf = (uint32_t *)&rdcu->sram[addr]; - + for (i = 0; i < size/sizeof(uint32_t); i++) { + uint32_t *sram_buf = (uint32_t *)&rdcu->sram[addr]; + sram_buf[i] = cpu_to_be32(buf[i]); } } @@ -2466,14 +2519,12 @@ int rdcu_sync_sram_mirror_parallel(uint32_t rx_addr, uint32_t rx_size, int rdcu_ctrl_init(void) { - rdcu = (struct rdcu_mirror *) malloc(sizeof(struct rdcu_mirror)); - if (!rdcu){ + rdcu = (struct rdcu_mirror *) calloc(1, sizeof(struct rdcu_mirror)); + if (!rdcu) { printf("Error allocating memory for the RDCU mirror\n"); return -1; } - memset(rdcu, 0, sizeof(struct rdcu_mirror)); - #if (__sparc__) rdcu->sram = (uint8_t *) 0x60000000; #else /* assume PC */ @@ -2485,7 +2536,7 @@ int rdcu_ctrl_init(void) } #endif - memset(rdcu->sram, 0, RDCU_SRAM_SIZE); - + memset(rdcu->sram, 0, RDCU_SRAM_SIZE); /* clear sram buffer */ + return 0; } diff --git a/lib/rdcu_pkt_to_file.c b/lib/rdcu_pkt_to_file.c index 0c71c8e..ed10778 100644 --- a/lib/rdcu_pkt_to_file.c +++ b/lib/rdcu_pkt_to_file.c @@ -17,7 +17,7 @@ * * This library provided a rmap_rx and rmap_tx function for the rdcu_rmap * library to write generated packets into text files. - * + * @warning this part of the software is not intended to run on-board on the ICU. */ #include <stdint.h> @@ -28,11 +28,11 @@ #include <errno.h> #include <sys/stat.h> -#include "rdcu_pkt_to_file.h" -#include "cmp_rdcu_extended.h" -#include "rdcu_rmap.h" -#include "rdcu_ctrl.h" -#include "rdcu_cmd.h" +#include <rdcu_pkt_to_file.h> +#include <cmp_rdcu_extended.h> +#include <rdcu_rmap.h> +#include <rdcu_ctrl.h> +#include <rdcu_cmd.h> /* Name of directory were the RMAP packages are stored */ static char tc_folder_dir[MAX_TC_FOLDER_DIR_LEN] = "TC_FILES"; diff --git a/lib/rdcu_rmap.c b/lib/rdcu_rmap.c index f6035cb..b314b47 100644 --- a/lib/rdcu_rmap.c +++ b/lib/rdcu_rmap.c @@ -70,7 +70,7 @@ #include <rmap.h> #include <rdcu_rmap.h> - +#define RDCU_CONFIG_DEBUG 0 static uint8_t rdcu_addr; static uint8_t icu_addr; @@ -116,8 +116,8 @@ static size_t data_mtu; /* maximum data transfer size per unit */ * Every time a slot is retrieved, the "pending" counter is incremented to * have a fast indicator of the synchronisation status, i.e. if "pending" * is not set, the synchronisation procedure is complete and the local data may - * be read, or the remote data has been written and further commands may may - * be issued. + * be read, or the remote data has been written and further commands may be + * issued. * * The local (mirror) start address of the requested remote address is stored * into the same slot in the "local_addr" array, so we'll know where to put the @@ -261,7 +261,7 @@ static int rdcu_process_rx(void) cnt++; - if ((0)) + if (RDCU_CONFIG_DEBUG) rmap_parse_pkt(spw_pckt); /* convert format */ @@ -276,25 +276,20 @@ static int rdcu_process_rx(void) local_addr = trans_log_get_addr(rp->tr_id); if (!local_addr) { - printf("warning: response packet received not in " - "transaction log\n"); + printf("Warning: response packet received not in transaction log\n"); rmap_erase_packet(rp); continue; } - if (rp->data_len & 0x3) { - printf("Error: response packet data size is not a " - "multiple of 4, transaction dropped\n"); + printf("Error: response packet data size is not a multiple of 4, transaction dropped\n"); trans_log_release_slot(rp->tr_id); rmap_erase_packet(rp); return -1; } - if (rp->data_len) { - uint8_t crc8; /* convert endianess if needed */ @@ -308,13 +303,9 @@ static int rdcu_process_rx(void) } #endif /* __BYTE_ORDER__ */ - crc8 = rmap_crc8(rp->data, rp->data_len); - if (crc8 != rp->data_crc) { - - printf("Error: data CRC8 mismatch, data invalid or " - "packet truncated. Transaction dropped\n"); + printf("Error: data CRC8 mismatch, data invalid or packet truncated. Transaction dropped\n"); trans_log_release_slot(rp->tr_id); rmap_erase_packet(rp); @@ -353,7 +344,7 @@ int rdcu_submit_tx(const uint8_t *cmd, int cmd_size, if (!rmap_tx) return -1; - if ((0)) + if (RDCU_CONFIG_DEBUG) printf("Transmitting RMAP command\n"); if (rmap_tx(cmd, cmd_size, dpath_len, data, data_size)) { @@ -413,7 +404,7 @@ int rdcu_gen_cmd(uint16_t trans_id, uint8_t *cmd, return n; } - memset(cmd, 0, n); + memset(cmd, 0, n); /* clear command buffer */ n = rmap_build_hdr(pkt, cmd); @@ -476,8 +467,7 @@ int rdcu_sync(int (*fn)(uint16_t trans_id, uint8_t *cmd), /* convert endianess if needed */ #if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) - if (data_len) - { + if (data_len) { int i; uint32_t *tmp_buf = alloca(data_len); uint32_t *p = (uint32_t *) addr; @@ -530,7 +520,7 @@ int rdcu_sync_data(int (*fn)(uint16_t trans_id, uint8_t *cmd, slot = trans_log_grab_slot(data); if (slot < 0) { - if ((0)) + if (RDCU_CONFIG_DEBUG) printf("Error: all slots busy!\n"); return 1; } @@ -782,7 +772,7 @@ int rdcu_rmap_sync_status(void) void rdcu_rmap_reset_log(void) { - memset(trans_log.in_use, 0, TRANS_LOG_SIZE); + memset(trans_log.in_use, 0, sizeof(trans_log.in_use)); /* clear in_use buffer */ trans_log.pending = 0; } diff --git a/lib/rmap.c b/lib/rmap.c index f8b6eb0..703add5 100644 --- a/lib/rmap.c +++ b/lib/rmap.c @@ -178,7 +178,7 @@ struct rmap_pkt *rmap_create_packet(void) struct rmap_pkt *pkt; - pkt = (struct rmap_pkt *) calloc(sizeof(struct rmap_pkt), 1); + pkt = (struct rmap_pkt *) calloc(1, sizeof(struct rmap_pkt)); if (pkt) pkt->proto_id = RMAP_PROTOCOL_ID; @@ -206,7 +206,7 @@ void rmap_destroy_packet(struct rmap_pkt *pkt) * @param pkt a struct rmap_pkt * * @note this will attempt to deallocate any pointer references assigned by the - * user + * user * @warning use with care */ @@ -563,7 +563,7 @@ struct rmap_pkt *rmap_pkt_from_buffer(uint8_t *buf, uint32_t len) pkt->src = buf[RMAP_SRC_ADDR + n]; pkt->tr_id = ((uint16_t) buf[RMAP_TRANS_ID_BYTE0 + n] << 8) | - (uint16_t) buf[RMAP_TRANS_ID_BYTE1 + n]; + (uint16_t) buf[RMAP_TRANS_ID_BYTE1 + n]; /* commands have a data address */ if (pkt->ri.cmd_resp) { @@ -576,10 +576,9 @@ struct rmap_pkt *rmap_pkt_from_buffer(uint8_t *buf, uint32_t len) /* all headers have data length unless they are a write reply */ if (!(!pkt->ri.cmd_resp && (pkt->ri.cmd & (RMAP_CMD_BIT_WRITE)))) { - pkt->data_len = ((uint32_t) buf[RMAP_DATALEN_BYTE0 + n] << 16) | ((uint32_t) buf[RMAP_DATALEN_BYTE1 + n] << 8) | - (uint32_t) buf[RMAP_DATALEN_BYTE2 + n]; + (uint32_t) buf[RMAP_DATALEN_BYTE2 + n]; } pkt->hdr_crc = buf[RMAP_HEADER_CRC]; @@ -587,7 +586,7 @@ struct rmap_pkt *rmap_pkt_from_buffer(uint8_t *buf, uint32_t len) if (pkt->data_len) { if (len < RMAP_DATA_START + n + pkt->data_len + 1) { /* +1 for data CRC */ printf("buffer len is smaller than the contained RMAP packet; buf len: %u bytes vs RMAP: %zu bytes needed\n", - len , RMAP_DATA_START + n + pkt->data_len); + len, RMAP_DATA_START + n + pkt->data_len); goto error; } if (len > RMAP_DATA_START + n + pkt->data_len + 1) /* +1 for data CRC */ @@ -702,7 +701,7 @@ static void rmap_process_read_reply(uint8_t *pkt) for (i = 0; i < len; i++) printf("%02x:", pkt[RMAP_DATA_START + i]); - printf("\b \n"); + printf("\b\n"); } -- GitLab From f934932225d45ca266c34db4c5b5965bcad616f2 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Tue, 12 Jul 2022 11:03:54 +0200 Subject: [PATCH 23/46] Unification of buffer names between RDCU and ICU compression Fix some typos Use default values if .rdcu_pkt_mode_cfg file is not available --- cmp_tool.c | 2 +- include/cmp_io.h | 3 +++ include/cmp_rdcu.h | 4 +-- include/rdcu_pkt_to_file.h | 5 ++++ lib/cmp_io.c | 2 +- lib/cmp_rdcu.c | 50 ++++++++++++++++++++------------------ lib/rdcu_pkt_to_file.c | 13 ++++++---- lib/rdcu_rmap.c | 2 +- 8 files changed, 47 insertions(+), 34 deletions(-) diff --git a/cmp_tool.c b/cmp_tool.c index c30ae6e..54e20ab 100644 --- a/cmp_tool.c +++ b/cmp_tool.c @@ -569,7 +569,7 @@ static int guess_cmp_pars(struct cmp_cfg *cfg, const char *guess_cmp_mode, } -/* generate packets to setup a RDCU compression */ +/* generate packets to setup an RDCU compression */ static int gen_rdcu_write_pkts(struct cmp_cfg *cfg) { int error; diff --git a/include/cmp_io.h b/include/cmp_io.h index 6de2222..873bf91 100644 --- a/include/cmp_io.h +++ b/include/cmp_io.h @@ -54,3 +54,6 @@ void print_cfg(const struct cmp_cfg *cfg, int rdcu_cfg); int atoui32(const char *dep_str, const char *val_str, uint32_t *red_val); int cmp_mode_parse(const char *cmp_mode_str, uint32_t *cmp_mode); + +enum cmp_data_type string2data_type(const char *data_type_str); +const char *data_type2string(enum cmp_data_type data_type); diff --git a/include/cmp_rdcu.h b/include/cmp_rdcu.h index 6273549..780c869 100644 --- a/include/cmp_rdcu.h +++ b/include/cmp_rdcu.h @@ -55,9 +55,9 @@ int rdcu_read_cmp_status(struct cmp_status *status); int rdcu_read_cmp_info(struct cmp_info *info); -int rdcu_read_cmp_bitstream(const struct cmp_info *info, void *output_buf); +int rdcu_read_cmp_bitstream(const struct cmp_info *info, void *compressed_data); -int rdcu_read_model(const struct cmp_info *info, void *model_buf); +int rdcu_read_model(const struct cmp_info *info, void *updated_model); int rdcu_interrupt_compression(void); diff --git a/include/rdcu_pkt_to_file.h b/include/rdcu_pkt_to_file.h index 1c350c8..b0f0558 100644 --- a/include/rdcu_pkt_to_file.h +++ b/include/rdcu_pkt_to_file.h @@ -27,6 +27,11 @@ #define MAX_TC_FOLDER_DIR_LEN 256 +/* default values when no .rdcu_pkt_mode_cfg file is available */ +#define DEF_ICU_ADDR 0xA7 +#define DEF_RDCU_ADDR 0xFE +#define DEF_MTU 4224 + int init_rmap_pkt_to_file(void); void set_tc_folder_dir(const char *dir_name); diff --git a/lib/cmp_io.c b/lib/cmp_io.c index 0e74c0d..9e4eb3a 100644 --- a/lib/cmp_io.c +++ b/lib/cmp_io.c @@ -200,7 +200,7 @@ int write_input_data_to_file(void *data, uint32_t data_size, enum cmp_data_type if (verbose) { printf("\n\n"); for (i = 0; i < data_size; i++) { - printf("%02X ", tmp_buf[i]); + printf("%02X", tmp_buf[i]); if ((i + 1) % 16 == 0) printf("\n"); else diff --git a/lib/cmp_rdcu.c b/lib/cmp_rdcu.c index 336fc87..ff4eb6c 100644 --- a/lib/cmp_rdcu.c +++ b/lib/cmp_rdcu.c @@ -82,13 +82,13 @@ static void sync(void) /** * @brief check if the compression data product type, compression mode, model - * value and the lossy rounding parameters are valid for a RDCU compression + * value and the lossy rounding parameters are valid for an RDCU compression * * @param cfg pointer to a compression configuration containing the compression * data product type, compression mode, model value and the rounding parameters * * @returns 0 if the compression data type, compression mode, model value and - * the lossy rounding parameters are valid for a RDCU compression, non-zero + * the lossy rounding parameters are valid for an RDCU compression, non-zero * if parameters are invalid */ @@ -131,7 +131,7 @@ static int rdcu_cfg_gen_par_is_invalid(const struct cmp_cfg *cfg) /** - * @brief create a RDCU compression configuration + * @brief create an RDCU compression configuration * * @param data_type compression data product types * @param cmp_mode compression mode @@ -401,7 +401,7 @@ int rdcu_cfg_buffers(struct cmp_cfg *cfg, uint16_t *data_to_compress, /** * @brief check if the Golomb and spillover threshold parameter combination is - * invalid for a RDCU compression + * invalid for an RDCU compression * @note also checked the adaptive Golomb and spillover threshold parameter combinations * * @param cfg a pointer to a compression configuration @@ -519,7 +519,7 @@ int rdcu_cfg_imagette(struct cmp_cfg *cfg, /** - * @brief check if the compressor configuration is invalid for a RDCU compression, + * @brief check if the compressor configuration is invalid for an RDCU compression, * see the user manual for more information (PLATO-UVIE-PL-UM-0001). * * @param cfg pointer to a compression configuration contains all parameters @@ -677,10 +677,13 @@ int rdcu_start_compression(void) * * @param cfg configuration contains all parameters required for compression * - * @note when using the 1d-differencing mode or the raw mode (cmp_mode = 0,2,4), - * the model parameters (model_value, model_buf, rdcu_model_adr) are ignored - * @note the validity of the cfg structure is checked before the compression is - * started + * @note Before the rdcu_compress function can be used, an initialization of + * the RMAP library is required. This is achieved with the functions + * rdcu_ctrl_init() and rdcu_rmap_init(). + * @note When using the 1d-differencing mode or the raw mode (cmp_mode = 0,2,4), + * the model parameters (model_value, model_of_data, rdcu_model_adr) are ignored. + * @note The validity of the cfg structure is checked before the compression is + * started. * * @returns 0 on success, error otherwise */ @@ -761,7 +764,7 @@ int rdcu_read_cmp_status(struct cmp_status *status) /** - * @brief read out the metadata of a RDCU compression + * @brief read out the metadata of an RDCU compression * * @param info compression information contains the metadata of a compression * @@ -817,15 +820,14 @@ int rdcu_read_cmp_info(struct cmp_info *info) /** * @brief read the compressed bitstream from the RDCU SRAM * - * @param info compression information contains the metadata of a compression - * - * @param output_buf the buffer to store the bitstream (if NULL, the required - * size is returned) + * @param info compression information contains the metadata of a compression + * @param compressed_data the buffer to store the bitstream (if NULL, the + * required size is returned) * * @returns the number of bytes read, < 0 on error */ -int rdcu_read_cmp_bitstream(const struct cmp_info *info, void *output_buf) +int rdcu_read_cmp_bitstream(const struct cmp_info *info, void *compressed_data) { uint32_t s; @@ -835,7 +837,7 @@ int rdcu_read_cmp_bitstream(const struct cmp_info *info, void *output_buf) /* calculate the need bytes for the bitstream */ s = cmp_bit_to_4byte(info->cmp_size); - if (output_buf == NULL) + if (compressed_data == NULL) return (int)s; if (rdcu_sync_sram_to_mirror(info->rdcu_cmp_adr_used, s, @@ -845,22 +847,22 @@ int rdcu_read_cmp_bitstream(const struct cmp_info *info, void *output_buf) /* wait for it */ sync(); - return rdcu_read_sram(output_buf, info->rdcu_cmp_adr_used, s); + return rdcu_read_sram(compressed_data, info->rdcu_cmp_adr_used, s); } /** - * @brief read the model from the RDCU SRAM + * @brief read the updated model from the RDCU SRAM * - * @param info compression information contains the metadata of a compression + * @param info compression information contains the metadata of a compression * - * @param model_buf the buffer to store the model (if NULL, the required size - * is returned) + * @param updated_model the buffer to store the updated model (if NULL, the required size + * is returned) * * @returns the number of bytes read, < 0 on error */ -int rdcu_read_model(const struct cmp_info *info, void *model_buf) +int rdcu_read_model(const struct cmp_info *info, void *updated_model) { uint32_t s; @@ -870,7 +872,7 @@ int rdcu_read_model(const struct cmp_info *info, void *model_buf) /* calculate the need bytes for the model */ s = info->samples_used * IMA_SAM2BYT; - if (model_buf == NULL) + if (updated_model == NULL) return (int)s; if (rdcu_sync_sram_to_mirror(info->rdcu_new_model_adr_used, (s+3) & ~3U, @@ -880,7 +882,7 @@ int rdcu_read_model(const struct cmp_info *info, void *model_buf) /* wait for it */ sync(); - return rdcu_read_sram(model_buf, info->rdcu_new_model_adr_used, s); + return rdcu_read_sram(updated_model, info->rdcu_new_model_adr_used, s); } diff --git a/lib/rdcu_pkt_to_file.c b/lib/rdcu_pkt_to_file.c index ed10778..9e80125 100644 --- a/lib/rdcu_pkt_to_file.c +++ b/lib/rdcu_pkt_to_file.c @@ -311,8 +311,11 @@ int init_rmap_pkt_to_file(void) uint8_t icu_addr, rdcu_addr; int mtu; - if (read_rdcu_pkt_mode_cfg(&icu_addr, &rdcu_addr, &mtu)) - return -1; + if (read_rdcu_pkt_mode_cfg(&icu_addr, &rdcu_addr, &mtu)) { + icu_addr = DEF_ICU_ADDR; + rdcu_addr = DEF_RDCU_ADDR; + mtu = DEF_MTU; + } rdcu_ctrl_init(); rdcu_set_source_logical_address(icu_addr); rdcu_set_destination_logical_address(rdcu_addr); @@ -323,7 +326,7 @@ int init_rmap_pkt_to_file(void) /** - * @brief generate the rmap packets to set up a RDCU compression + * @brief generate the rmap packets to set up an RDCU compression * @note note that the initialization function init_rmap_pkt_to_file() must be * executed before * @note the configuration of the ICU_ADDR, RDCU_ADDR, MTU settings are in the @@ -363,7 +366,7 @@ int gen_write_rdcu_pkts(const struct cmp_cfg *cfg) /** - * @brief generate the rmap packets to read the result of a RDCU compression + * @brief generate the rmap packets to read the result of an RDCU compression * @note note that the initialization function init_rmap_pkt_to_file() must be * executed before * @note the configuration of the ICU_ADDR, RDCU_ADDR, MTU settings are in the @@ -434,7 +437,7 @@ int gen_read_rdcu_pkts(const struct cmp_info *info) /** - * @brief generate the rmap packets to set up a RDCU compression, read the + * @brief generate the rmap packets to set up an RDCU compression, read the * bitstream and the updated model in parallel to write the data to compressed * and the model and start the compression * @note the compressed data are read from cfg->rdcu_buffer_adr with the length diff --git a/lib/rdcu_rmap.c b/lib/rdcu_rmap.c index b314b47..e4ec4b6 100644 --- a/lib/rdcu_rmap.c +++ b/lib/rdcu_rmap.c @@ -490,7 +490,7 @@ int rdcu_sync(int (*fn)(uint16_t trans_id, uint8_t *cmd), /** * @brief submit a data sync command * - * @param fn a RDCU data transfer generation function + * @param fn an RDCU data transfer generation function * @param addr the remote address * @param data the local data address * @param data_len the length of the data payload -- GitLab From 1da2d6052eca2d0dd172863108cee715079abe13 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Thu, 1 Sep 2022 12:39:39 +0200 Subject: [PATCH 24/46] fix a bug in the compress_multi_entry_hdr() function reduce the max golomb_par (cmp_par) because the entity field has 16 bits split get_max_spill() function in 2 for RDCU cmp_rdcu_max_spill() and ICU cmp_icu_max_spill() fix some typos refactoring pad_bitstream() and cmp_data_to_big_endian() configure_encoder_setup() functions refactoring RAW compression update tests --- include/cmp_icu.h | 10 +- include/cmp_rdcu.h | 4 +- include/cmp_support.h | 19 +- lib/cmp_guess.c | 4 +- lib/cmp_icu.c | 202 +++-- lib/cmp_rdcu.c | 53 +- lib/cmp_support.c | 143 +-- test/cmp_icu/test_cmp_icu.c | 1676 ++++++++++++++++++++++++++++++++++- 8 files changed, 1895 insertions(+), 216 deletions(-) diff --git a/include/cmp_icu.h b/include/cmp_icu.h index 01e528c..8b68fcb 100644 --- a/include/cmp_icu.h +++ b/include/cmp_icu.h @@ -22,15 +22,7 @@ #include <cmp_support.h> - -/* return code if the bitstream buffer is too small to store the whole bitstream */ -#define CMP_ERROR_SAMLL_BUF -2 - -/* return code if the value or the model is bigger than the max_used_bits - * parameter allows - */ -#define CMP_ERROR_HIGH_VALUE -3 - +#define CMP_PAR_UNUSED 0 /* create and setup a compression configuration */ struct cmp_cfg cmp_cfg_icu_create(enum cmp_data_type data_type, enum cmp_mode cmp_mode, diff --git a/include/cmp_rdcu.h b/include/cmp_rdcu.h index 780c869..93da3d0 100644 --- a/include/cmp_rdcu.h +++ b/include/cmp_rdcu.h @@ -47,8 +47,6 @@ int rdcu_cfg_imagette(struct cmp_cfg *cfg, uint32_t ap1_golomb_par, uint32_t ap1_spillover_par, uint32_t ap2_golomb_par, uint32_t ap2_spillover_par); -int rdcu_cmp_cfg_is_invalid(const struct cmp_cfg *cfg); - int rdcu_compress_data(const struct cmp_cfg *cfg); int rdcu_read_cmp_status(struct cmp_status *status); @@ -64,4 +62,6 @@ int rdcu_interrupt_compression(void); void rdcu_enable_interrput_signal(void); void rdcu_disable_interrput_signal(void); +int rdcu_cmp_cfg_is_invalid(const struct cmp_cfg *cfg); + #endif /* _CMP_RDCU_H_ */ diff --git a/include/cmp_support.h b/include/cmp_support.h index f5e0b38..5c85a99 100644 --- a/include/cmp_support.h +++ b/include/cmp_support.h @@ -23,6 +23,14 @@ #include <stddef.h> +/* return code if the bitstream buffer is too small to store the whole bitstream */ +#define CMP_ERROR_SAMLL_BUF -2 + +/* return code if the value or the model is bigger than the max_used_bits + * parameter allows + */ +#define CMP_ERROR_HIGH_VALUE -3 + #define CMP_LOSSLESS 0 #define CMP_PAR_UNUNSED 0 @@ -36,13 +44,13 @@ #define MAX_RDCU_GOLOMB_PAR 63U #define MIN_RDCU_SPILL 2U #define MAX_RDCU_ROUND 2U -/* for maximum spill value look at get_max_spill function */ +/* for maximum spill value look at cmp_rdcu_max_spill function */ /* valid compression parameter ranges for ICU compression */ #define MIN_ICU_GOLOMB_PAR 1U -#define MAX_ICU_GOLOMB_PAR 0x80000000U +#define MAX_ICU_GOLOMB_PAR UINT16_MAX /* the compression entity dos not allow larger values */ #define MIN_ICU_SPILL 2U -/* for maximum spill value look at get_max_spill function */ +/* for maximum spill value look at cmp_icu_max_spill function */ #define MAX_ICU_ROUND 3U #define MAX_STUFF_CMP_PAR 32U @@ -86,7 +94,7 @@ /* defined compression data product types */ enum cmp_data_type { - DATA_TYPE_UNKOWN, + DATA_TYPE_UNKNOWN, DATA_TYPE_IMAGETTE, DATA_TYPE_IMAGETTE_ADAPTIVE, DATA_TYPE_SAT_IMAGETTE, @@ -235,7 +243,8 @@ int cmp_cfg_icu_buffers_is_valid(const struct cmp_cfg *cfg); int cmp_cfg_imagette_is_valid(const struct cmp_cfg *cfg); int cmp_cfg_fx_cob_is_valid(const struct cmp_cfg *cfg); int cmp_cfg_aux_is_valid(const struct cmp_cfg *cfg); -uint32_t get_max_spill(unsigned int golomb_par, enum cmp_data_type); +uint32_t cmp_rdcu_max_spill(unsigned int golomb_par); +uint32_t cmp_icu_max_spill(unsigned int cmp_par); int cmp_data_type_valid(enum cmp_data_type data_type); int rdcu_supported_data_type_is_used(enum cmp_data_type data_type); diff --git a/lib/cmp_guess.c b/lib/cmp_guess.c index 3107aaf..40e3ebe 100644 --- a/lib/cmp_guess.c +++ b/lib/cmp_guess.c @@ -91,7 +91,7 @@ uint32_t cmp_rdcu_get_good_spill(unsigned int golomb_par, enum cmp_mode cmp_mode 405, 411, 418, 424, 431, 452 }; if (zero_escape_mech_is_used(cmp_mode)) - return get_max_spill(golomb_par, DATA_TYPE_IMAGETTE); + return cmp_rdcu_max_spill(golomb_par); if (cmp_mode == CMP_MODE_MODEL_MULTI) { if (golomb_par > MAX_RDCU_GOLOMB_PAR) @@ -172,7 +172,7 @@ static uint32_t brute_force(struct cmp_cfg *cfg) fflush(stdout); for (g = MIN_RDCU_GOLOMB_PAR; g < MAX_RDCU_GOLOMB_PAR; g++) { - for (s = MIN_RDCU_SPILL; s < get_max_spill(g, cfg->data_type); s++) { + for (s = MIN_RDCU_SPILL; s < cmp_rdcu_max_spill(g); s++) { cfg->golomb_par = g; cfg->spill = s; diff --git a/lib/cmp_icu.c b/lib/cmp_icu.c index 2d11006..c6d4a27 100644 --- a/lib/cmp_icu.c +++ b/lib/cmp_icu.c @@ -39,6 +39,10 @@ #include <cmp_icu.h> +/* maximum used bits registry */ +extern struct cmp_max_used_bits max_used_bits; + + /* pointer to a code word generation function */ typedef uint32_t (*generate_cw_f_pt)(uint32_t value, uint32_t encoder_par1, uint32_t encoder_par2, uint32_t *cw); @@ -46,7 +50,7 @@ typedef uint32_t (*generate_cw_f_pt)(uint32_t value, uint32_t encoder_par1, /* structure to hold a setup to encode a value */ struct encoder_setupt { - generate_cw_f_pt generate_cw_f; /* pointer to the code word generation function */ + generate_cw_f_pt generate_cw_f; /* pointer to the code word encoder */ int (*encode_method_f)(uint32_t data, uint32_t model, int stream_len, const struct encoder_setupt *setup); /* pointer to the encoding function */ uint32_t *bitstream_adr; /* start address of the compressed data bitstream */ @@ -60,15 +64,15 @@ struct encoder_setupt { /** - * @brief create a ICU compression configuration + * @brief create an ICU compression configuration * - * @param data_type compression data product types + * @param data_type compression data product type * @param cmp_mode compression mode - * @param model_value model weighting parameter (only need for model compression mode) + * @param model_value model weighting parameter (only needed for model compression mode) * @param lossy_par lossy rounding parameter (use CMP_LOSSLESS for lossless compression) * - * @returns compression configuration containing the chosen parameters; - * on error the data_type record is set to DATA_TYPE_UNKOWN + * @returns a compression configuration containing the chosen parameters; + * on error the data_type record is set to DATA_TYPE_UNKNOWN */ struct cmp_cfg cmp_cfg_icu_create(enum cmp_data_type data_type, enum cmp_mode cmp_mode, @@ -86,20 +90,20 @@ struct cmp_cfg cmp_cfg_icu_create(enum cmp_data_type data_type, enum cmp_mode cm cfg_valid = cmp_cfg_icu_gen_par_is_valid(&cfg); if (!cfg_valid) - cfg.data_type = DATA_TYPE_UNKOWN; + cfg.data_type = DATA_TYPE_UNKNOWN; return cfg; } /** - * @brief setup of the different data buffers for an ICU compression + * @brief setup the different data buffers for an ICU compression * * @param cfg pointer to a compression configuration (created * with the cmp_cfg_icu_create() function) * @param data_to_compress pointer to the data to be compressed * @param data_samples length of the data to be compressed measured in - * data samples/entitys (multi entity header not + * data samples/entitys (collection header not * included by imagette data) * @param model_of_data pointer to model data buffer (can be NULL if no * model compression mode is used) @@ -142,7 +146,7 @@ size_t cmp_cfg_icu_buffers(struct cmp_cfg *cfg, void *data_to_compress, * @brief set up the configuration parameters for an ICU imagette compression * * @param cfg pointer to a compression configuration (created - * with the cmp_cfg_icu_create() function) + * by the cmp_cfg_icu_create() function) * @param cmp_par imagette compression parameter (Golomb parameter) * @param spillover_par imagette spillover threshold parameter * @@ -166,11 +170,11 @@ int cmp_cfg_icu_imagette(struct cmp_cfg *cfg, uint32_t cmp_par, /** - * @brief set up of the configuration parameters for a flux/COB compression + * @brief set up the configuration parameters for a flux/COB compression * @note not all parameters are needed for every flux/COB compression data type * * @param cfg pointer to a compression configuration (created - * with the cmp_cfg_icu_create() function) + * by the cmp_cfg_icu_create() function) * @param cmp_par_exp_flags exposure flags compression parameter * @param spillover_exp_flags exposure flags spillover threshold parameter * @param cmp_par_fx normal flux compression parameter @@ -220,13 +224,13 @@ int cmp_cfg_fx_cob(struct cmp_cfg *cfg, /** - * @brief set up of the configuration parameters for an auxiliary science data compression + * @brief set up the configuration parameters for an auxiliary science data compression * @note auxiliary compression data types are: DATA_TYPE_OFFSET, DATA_TYPE_BACKGROUND, DATA_TYPE_SMEARING, DATA_TYPE_F_CAM_OFFSET, DATA_TYPE_F_CAM_BACKGROUND - * @note not all parameters are needed for the every auxiliary compression data types + * @note not all parameters are needed for the every auxiliary compression data type * - * @param cfg pointer to a compression configuration ( - * created with the cmp_cfg_icu_create() function) + * @param cfg pointer to a compression configuration (created + * with the cmp_cfg_icu_create() function) * @param cmp_par_mean mean compression parameter * @param spillover_mean mean spillover threshold parameter * @param cmp_par_variance variance compression parameter @@ -739,29 +743,41 @@ static int configure_encoder_setup(struct encoder_setupt *setup, if (!cfg) return -1; - setup->encoder_par1 = cmp_par; - setup->spillover_par = spillover; if (max_data_bits > 32) { debug_print("Error: max_data_bits parameter is bigger than 32 bits.\n"); return -1; } + + memset(setup, 0, sizeof(*setup)); + + setup->encoder_par1 = cmp_par; setup->max_data_bits = max_data_bits; setup->lossy_par = lossy_par; + setup->bitstream_adr = cfg->icu_output_buf; + setup->max_stream_len = cmp_buffer_length_to_bits(cfg->buffer_length, cfg->data_type); + + if (cfg->cmp_mode != CMP_MODE_STUFF) { + if (ilog_2(cmp_par) < 0) + return -1; + setup->encoder_par2 = (uint32_t)ilog_2(cmp_par); + + setup->spillover_par = spillover; + + /* for encoder_par1 which are a power of two we can use the faster rice_encoder */ + if (is_a_pow_of_2(setup->encoder_par1)) + setup->generate_cw_f = &rice_encoder; + else + setup->generate_cw_f = &golomb_encoder; + } switch (cfg->cmp_mode) { case CMP_MODE_MODEL_ZERO: case CMP_MODE_DIFF_ZERO: setup->encode_method_f = &encode_value_zero; - if (ilog_2(cmp_par) < 0) - return -1; - setup->encoder_par2 = (uint32_t)ilog_2(cmp_par); break; case CMP_MODE_MODEL_MULTI: case CMP_MODE_DIFF_MULTI: setup->encode_method_f = &encode_value_multi; - if (ilog_2(cmp_par) < 0) - return -1; - setup->encoder_par2 = (uint32_t)ilog_2(cmp_par); break; case CMP_MODE_STUFF: setup->encode_method_f = &encode_value_none; @@ -771,14 +787,6 @@ static int configure_encoder_setup(struct encoder_setupt *setup, return -1; } - /* for encoder_par1 which are a power of two we can use the faster rice_encoder */ - if (is_a_pow_of_2(setup->encoder_par1)) - setup->generate_cw_f = &rice_encoder; - else - setup->generate_cw_f = &golomb_encoder; - - setup->bitstream_adr = cfg->icu_output_buf; - setup->max_stream_len = cmp_buffer_length_to_bits(cfg->buffer_length, cfg->data_type); return 0; } @@ -808,9 +816,6 @@ static int compress_imagette(const struct cmp_cfg *cfg) uint16_t *next_model_p = data_buf; uint16_t *up_model_buf = NULL; - if (cfg->samples == 0) - return 0; - if (model_mode_is_used(cfg->cmp_mode)) { model = model_buf[0]; next_model_p = &model_buf[1]; @@ -843,10 +848,10 @@ static int compress_imagette(const struct cmp_cfg *cfg) * @brief compress the multi-entry packet header structure and sets the data, * model and up_model pointers to the data after the header * - * @param data pointer to a pointer pointing to the data to be compressed - * @param model pointer to a pointer pointing to the model of the data - * @param up_model pointer to a pointer pointing to the updated model buffer - * @param cfg pointer to the compression configuration structure + * @param data pointer to a pointer pointing to the data to be compressed + * @param model pointer to a pointer pointing to the model of the data + * @param up_model pointer to a pointer pointing to the updated model buffer + * @param compressed_data pointer to the compressed data buffer * * @returns the bit length of the bitstream on success; negative on error, * @@ -855,26 +860,23 @@ static int compress_imagette(const struct cmp_cfg *cfg) */ static int compress_multi_entry_hdr(void **data, void **model, void **up_model, - const struct cmp_cfg *cfg) + void *compressed_data) { - if (cfg->buffer_length < 1) - return -1; + if (*up_model) { + if (*data) + memcpy(*up_model, *data, MULTI_ENTRY_HDR_SIZE); + *up_model = (uint8_t *)*up_model + MULTI_ENTRY_HDR_SIZE; + } if (*data) { - if (cfg->icu_output_buf) - memcpy(cfg->icu_output_buf, *data, MULTI_ENTRY_HDR_SIZE); + if (compressed_data) + memcpy(compressed_data, *data, MULTI_ENTRY_HDR_SIZE); *data = (uint8_t *)*data + MULTI_ENTRY_HDR_SIZE; } if (*model) *model = (uint8_t *)*model + MULTI_ENTRY_HDR_SIZE; - if (*up_model) { - if (*data) - memcpy(*up_model, *data, MULTI_ENTRY_HDR_SIZE); - *up_model = (uint8_t *)*up_model + MULTI_ENTRY_HDR_SIZE; - } - return MULTI_ENTRY_HDR_SIZE * CHAR_BIT; } @@ -906,7 +908,7 @@ static int compress_s_fx(const struct cmp_cfg *cfg) up_model_buf = cfg->icu_new_model_buf; stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, - (void **)&up_model_buf, cfg); + (void **)&up_model_buf, cfg->icu_output_buf); if (model_mode_is_used(cfg->cmp_mode)) { model = model_buf[0]; @@ -978,7 +980,7 @@ static int compress_s_fx_efx(const struct cmp_cfg *cfg) up_model_buf = cfg->icu_new_model_buf; stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, - (void **)&up_model_buf, cfg); + (void **)&up_model_buf, cfg->icu_output_buf); if (model_mode_is_used(cfg->cmp_mode)) { model = model_buf[0]; @@ -1060,7 +1062,7 @@ static int compress_s_fx_ncob(const struct cmp_cfg *cfg) up_model_buf = cfg->icu_new_model_buf; stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, - (void **)&up_model_buf, cfg); + (void **)&up_model_buf, cfg->icu_output_buf); if (model_mode_is_used(cfg->cmp_mode)) { model = model_buf[0]; @@ -1149,7 +1151,7 @@ static int compress_s_fx_efx_ncob_ecob(const struct cmp_cfg *cfg) up_model_buf = cfg->icu_new_model_buf; stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, - (void **)&up_model_buf, cfg); + (void **)&up_model_buf, cfg->icu_output_buf); if (model_mode_is_used(cfg->cmp_mode)) { model = model_buf[0]; @@ -1263,7 +1265,7 @@ static int compress_f_fx(const struct cmp_cfg *cfg) up_model_buf = cfg->icu_new_model_buf; stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, - (void **)&up_model_buf, cfg); + (void **)&up_model_buf, cfg->icu_output_buf); if (model_mode_is_used(cfg->cmp_mode)) { model = model_buf[0]; @@ -1325,7 +1327,7 @@ static int compress_f_fx_efx(const struct cmp_cfg *cfg) up_model_buf = cfg->icu_new_model_buf; stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, - (void **)&up_model_buf, cfg); + (void **)&up_model_buf, cfg->icu_output_buf); if (model_mode_is_used(cfg->cmp_mode)) { model = model_buf[0]; @@ -1397,7 +1399,7 @@ static int compress_f_fx_ncob(const struct cmp_cfg *cfg) up_model_buf = cfg->icu_new_model_buf; stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, - (void **)&up_model_buf, cfg); + (void **)&up_model_buf, cfg->icu_output_buf); if (model_mode_is_used(cfg->cmp_mode)) { model = model_buf[0]; @@ -1475,7 +1477,7 @@ static int compress_f_fx_efx_ncob_ecob(const struct cmp_cfg *cfg) up_model_buf = cfg->icu_new_model_buf; stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, - (void **)&up_model_buf, cfg); + (void **)&up_model_buf, cfg->icu_output_buf); if (model_mode_is_used(cfg->cmp_mode)) { model = model_buf[0]; @@ -1579,7 +1581,7 @@ static int compress_l_fx(const struct cmp_cfg *cfg) up_model_buf = cfg->icu_new_model_buf; stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, - (void **)&up_model_buf, cfg); + (void **)&up_model_buf, cfg->icu_output_buf); if (model_mode_is_used(cfg->cmp_mode)) { model = model_buf[0]; @@ -1661,7 +1663,7 @@ static int compress_l_fx_efx(const struct cmp_cfg *cfg) up_model_buf = cfg->icu_new_model_buf; stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, - (void **)&up_model_buf, cfg); + (void **)&up_model_buf, cfg->icu_output_buf); if (model_mode_is_used(cfg->cmp_mode)) { model = model_buf[0]; @@ -1754,7 +1756,7 @@ static int compress_l_fx_ncob(const struct cmp_cfg *cfg) up_model_buf = cfg->icu_new_model_buf; stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, - (void **)&up_model_buf, cfg); + (void **)&up_model_buf, cfg->icu_output_buf); if (model_mode_is_used(cfg->cmp_mode)) { model = model_buf[0]; @@ -1870,7 +1872,7 @@ static int compress_l_fx_efx_ncob_ecob(const struct cmp_cfg *cfg) up_model_buf = cfg->icu_new_model_buf; stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, - (void **)&up_model_buf, cfg); + (void **)&up_model_buf, cfg->icu_output_buf); if (model_mode_is_used(cfg->cmp_mode)) { model = model_buf[0]; @@ -2010,7 +2012,7 @@ static int compress_nc_offset(const struct cmp_cfg *cfg) up_model_buf = cfg->icu_new_model_buf; stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, - (void **)&up_model_buf, cfg); + (void **)&up_model_buf, cfg->icu_output_buf); if (model_mode_is_used(cfg->cmp_mode)) { model = model_buf[0]; @@ -2081,7 +2083,7 @@ static int compress_nc_background(const struct cmp_cfg *cfg) up_model_buf = cfg->icu_new_model_buf; stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, - (void **)&up_model_buf, cfg); + (void **)&up_model_buf, cfg->icu_output_buf); if (model_mode_is_used(cfg->cmp_mode)) { model = model_buf[0]; @@ -2163,7 +2165,7 @@ static int compress_smearing(const struct cmp_cfg *cfg) up_model_buf = cfg->icu_new_model_buf; stream_len = compress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, - (void **)&up_model_buf, cfg); + (void **)&up_model_buf, cfg->icu_output_buf); if (model_mode_is_used(cfg->cmp_mode)) { model = model_buf[0]; @@ -2236,13 +2238,13 @@ static int pad_bitstream(const struct cmp_cfg *cfg, int cmp_size) if (cmp_size < 0) return cmp_size; - /* maximum length of the bitstream/icu_output_buf in bits */ - output_buf_len_bits = cmp_buffer_length_to_bits(cfg->buffer_length, cfg->data_type); - - /* no padding in RAW mode*/ + /* no padding in RAW mode; DIFFERENCE ENDIANNESS */ if (cfg->cmp_mode == CMP_MODE_RAW) return cmp_size; + /* maximum length of the bitstream/icu_output_buf in bits */ + output_buf_len_bits = cmp_buffer_length_to_bits(cfg->buffer_length, cfg->data_type); + n_pad_bits = 32 - ((unsigned int)cmp_size & 0x1FU); if (n_pad_bits < 32) { int n_bits = put_n_bits32(0, n_pad_bits, cmp_size, cfg->icu_output_buf, @@ -2264,37 +2266,41 @@ static int pad_bitstream(const struct cmp_cfg *cfg, int cmp_size) * @returns 0 on success; non-zero on failure */ -static int cmp_data_to_big_endian(const struct cmp_cfg *cfg, unsigned int cmp_size) +static int cmp_data_to_big_endian(const struct cmp_cfg *cfg, int cmp_size) { #if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) size_t i; uint32_t *p; + uint32_t s = (uint32_t)cmp_size; - if (cfg->cmp_mode == CMP_MODE_RAW) { - int err = cmp_input_big_to_cpu_endianness(cfg->icu_output_buf, - cmp_size/CHAR_BIT, cfg->data_type); - return err; - } + if (cmp_size < 0) + return cmp_size; - if (rdcu_supported_data_type_is_used(cfg->data_type)) { - p = cfg->icu_output_buf; + if (cfg->cmp_mode == CMP_MODE_RAW) { + if (cmp_input_big_to_cpu_endianness(cfg->icu_output_buf, + s/CHAR_BIT, cfg->data_type)) + cmp_size = -1; } else { - p = &cfg->icu_output_buf[MULTI_ENTRY_HDR_SIZE/sizeof(uint32_t)]; - cmp_size -= MULTI_ENTRY_HDR_SIZE * CHAR_BIT; - } + if (rdcu_supported_data_type_is_used(cfg->data_type)) { + p = cfg->icu_output_buf; + } else { + p = &cfg->icu_output_buf[MULTI_ENTRY_HDR_SIZE/sizeof(uint32_t)]; + s -= MULTI_ENTRY_HDR_SIZE * CHAR_BIT; + } - for (i = 0; i < cmp_bit_to_4byte(cmp_size)/sizeof(uint32_t); i++) - cpu_to_be32s(&p[i]); + for (i = 0; i < cmp_bit_to_4byte(s)/sizeof(uint32_t); i++) + cpu_to_be32s(&p[i]); + } #else /* do nothing data are already in big-endian */ (void)cfg; #endif /*__BYTE_ORDER__ */ - return 0; + return cmp_size; } /** - * @brief compress data on the ICU + * @brief compress data on the ICU in software * * @param cfg pointer to a compression configuration (created with the * cmp_cfg_icu_create() function, setup with the cmp_cfg_xxx() functions) @@ -2319,22 +2325,22 @@ int icu_compress_data(const struct cmp_cfg *cfg) if (cfg->samples == 0) /* nothing to compress we are done*/ return 0; - if (!cmp_cfg_is_valid(cfg)) - return -1; + if (raw_mode_is_used(cfg->cmp_mode)) + if (cfg->samples > cfg->buffer_length) + return CMP_ERROR_SAMLL_BUF; - if (model_mode_is_used(cfg->cmp_mode) && !cfg->model_buf) + if (!cmp_cfg_is_valid(cfg)) return -1; if (raw_mode_is_used(cfg->cmp_mode)) { - if (cfg->samples > cfg->buffer_length) { - cmp_size = CMP_ERROR_SAMLL_BUF; - } else { - cmp_size = cmp_cal_size_of_data(cfg->samples, cfg->data_type); - if (cfg->icu_output_buf) - memcpy(cfg->icu_output_buf, cfg->input_buf, cmp_size); - cmp_size *= CHAR_BIT; /* convert to bits */ - } + cmp_size = cmp_cal_size_of_data(cfg->samples, cfg->data_type); + if (cfg->icu_output_buf) + memcpy(cfg->icu_output_buf, cfg->input_buf, cmp_size); + cmp_size *= CHAR_BIT; /* convert to bits */ } else { + if (cfg->samples < cfg->buffer_length/3) + debug_print("Warning: The size of the compressed_data buffer is 3 times smaller than the data_to_compress. This is probably unintended.\n"); + switch (cfg->data_type) { case DATA_TYPE_IMAGETTE: case DATA_TYPE_IMAGETTE_ADAPTIVE: @@ -2396,16 +2402,16 @@ int icu_compress_data(const struct cmp_cfg *cfg) case DATA_TYPE_F_CAM_OFFSET: case DATA_TYPE_F_CAM_BACKGROUND: - case DATA_TYPE_UNKOWN: + case DATA_TYPE_UNKNOWN: default: debug_print("Error: Data type not supported.\n"); cmp_size = -1; } } - if (cfg->icu_output_buf && cmp_size > 0) { + if (cfg->icu_output_buf) { cmp_size = pad_bitstream(cfg, cmp_size); - cmp_data_to_big_endian(cfg, (unsigned int)cmp_size); + cmp_size = cmp_data_to_big_endian(cfg, cmp_size); } return cmp_size; diff --git a/lib/cmp_rdcu.c b/lib/cmp_rdcu.c index ff4eb6c..c3ffb7c 100644 --- a/lib/cmp_rdcu.c +++ b/lib/cmp_rdcu.c @@ -133,13 +133,13 @@ static int rdcu_cfg_gen_par_is_invalid(const struct cmp_cfg *cfg) /** * @brief create an RDCU compression configuration * - * @param data_type compression data product types + * @param data_type compression data product type * @param cmp_mode compression mode - * @param model_value model weighting parameter (only need for model compression mode) + * @param model_value model weighting parameter (only needed for model compression mode) * @param lossy_par lossy rounding parameter (use CMP_LOSSLESS for lossless compression) * - * @returns compression configuration containing the chosen parameters; - * on error the data_type record is set to DATA_TYPE_UNKOWN + * @returns a compression configuration containing the chosen parameters; + * on error the data_type record is set to DATA_TYPE_UNKNOWN */ struct cmp_cfg rdcu_cfg_create(enum cmp_data_type data_type, enum cmp_mode cmp_mode, @@ -155,7 +155,7 @@ struct cmp_cfg rdcu_cfg_create(enum cmp_data_type data_type, enum cmp_mode cmp_m cfg.round = lossy_par; if (rdcu_cfg_gen_par_is_invalid(&cfg)) - cfg.data_type = DATA_TYPE_UNKOWN; + cfg.data_type = DATA_TYPE_UNKNOWN; return cfg; } @@ -348,27 +348,26 @@ static int rdcu_cfg_buffers_is_invalid(const struct cmp_cfg *cfg) /** - *@brief setup of the different data buffers for an RDCU compression + * @brief setup of the different data buffers for an RDCU compression * * @param cfg pointer to a compression configuration (created * with the rdcu_cfg_create() function) * @param data_to_compress pointer to the data to be compressed (if NULL no * data transfer to the RDCU) * @param data_samples length of the data to be compressed measured in - * 16-bit data samples (ignoring the multi entity header) + * 16-bit data samples (ignoring the collection header) * @param model_of_data pointer to model data buffer (only needed for * model compression mode, if NULL no model data * transfer to the RDCU) - * @param rdcu_data_adr RDCU data to compress start address, the first - * data address in the RDCU SRAM - * @param rdcu_model_adr RDCU model start address, the first model address - * in the RDCU SRAM (only need for model compression mode) - * @param rdcu_new_model_adr RDCU new/updated model start address(can be the + * @param rdcu_data_adr RDCU SRAM data to compress start address + * @param rdcu_model_adr RDCU SRAM model start address (only need for + * model compression mode) + * @param rdcu_new_model_adr RDCU SRAM new/updated model start address(can be the * by the same as rdcu_model_adr for in-place model update) - * @param rdcu_buffer_adr RDCU compressed data start address, the first - * output data address in the RDCU SRAM + * @param rdcu_buffer_adr RDCU SRAM compressed data start address * @param rdcu_buffer_lenght length of the RDCU compressed data SRAM buffer - * in number of 16-bit samples + * measured in 16-bit units (same as data_samples) + * * @returns 0 if parameters are valid, non-zero if parameters are invalid */ @@ -441,9 +440,9 @@ static int rdcu_cfg_imagette_is_invalid(const struct cmp_cfg *cfg) cfg_invalid++; } - if (cfg->spill > get_max_spill(cfg->golomb_par, cfg->data_type)) { + if (cfg->spill > cmp_rdcu_max_spill(cfg->golomb_par)) { debug_print("Error: The selected spillover threshold value: %u is too large for the selected Golomb parameter: %u, the largest possible spillover value is: %u.\n", - cfg->spill, cfg->golomb_par, get_max_spill(cfg->golomb_par, cfg->data_type)); + cfg->spill, cfg->golomb_par, cmp_rdcu_max_spill(cfg->golomb_par)); cfg_invalid++; } @@ -453,9 +452,9 @@ static int rdcu_cfg_imagette_is_invalid(const struct cmp_cfg *cfg) cfg_invalid++; } - if (cfg->ap1_spill > get_max_spill(cfg->ap1_golomb_par, cfg->data_type)) { + if (cfg->ap1_spill > cmp_rdcu_max_spill(cfg->ap1_golomb_par)) { debug_print("Error: The selected adaptive 1 spillover threshold value: %u is too large for the selected adaptive 1 Golomb parameter: %u, the largest possible adaptive 1 spillover value is: %u.\n", - cfg->ap1_spill, cfg->ap1_golomb_par, get_max_spill(cfg->ap1_golomb_par, cfg->data_type)); + cfg->ap1_spill, cfg->ap1_golomb_par, cmp_rdcu_max_spill(cfg->ap1_golomb_par)); cfg_invalid++; } @@ -465,9 +464,9 @@ static int rdcu_cfg_imagette_is_invalid(const struct cmp_cfg *cfg) cfg_invalid++; } - if (cfg->ap2_spill > get_max_spill(cfg->ap2_golomb_par, cfg->data_type)) { + if (cfg->ap2_spill > cmp_rdcu_max_spill(cfg->ap2_golomb_par)) { debug_print("Error: The selected adaptive 2 spillover threshold value: %u is too large for the selected adaptive 2 Golomb parameter: %u, the largest possible adaptive 2 spillover value is: %u.\n", - cfg->ap2_spill, cfg->ap2_golomb_par, get_max_spill(cfg->ap2_golomb_par, cfg->data_type)); + cfg->ap2_spill, cfg->ap2_golomb_par, cmp_rdcu_max_spill(cfg->ap2_golomb_par)); cfg_invalid++; } @@ -484,12 +483,12 @@ static int rdcu_cfg_imagette_is_invalid(const struct cmp_cfg *cfg) * * @param cfg pointer to a compression configuration (created * with the rdcu_cfg_create() function) - * @param golomb_par imagette compression parameter (Golomb parameter) + * @param golomb_par imagette compression parameter * @param spillover_par imagette spillover threshold parameter - * @param ap1_golomb_par adaptive 1 imagette compression parameter (ap1_golomb parameter) + * @param ap1_golomb_par adaptive 1 imagette compression parameter * @param ap1_spillover_par adaptive 1 imagette spillover threshold parameter - * @param ap2_golomb_par adaptive 2 imagette compression parameter (ap2_golomb parameter) - * @param ap2_spillover_par adaptive 1 imagette spillover threshold parameter + * @param ap2_golomb_par adaptive 2 imagette compression parameter + * @param ap2_spillover_par adaptive 2 imagette spillover threshold parameter * * @returns 0 if parameters are valid, non-zero if parameters are invalid */ @@ -677,11 +676,9 @@ int rdcu_start_compression(void) * * @param cfg configuration contains all parameters required for compression * - * @note Before the rdcu_compress function can be used, an initialization of + * @note Before the rdcu_compress function can be used, an initialisation of * the RMAP library is required. This is achieved with the functions * rdcu_ctrl_init() and rdcu_rmap_init(). - * @note When using the 1d-differencing mode or the raw mode (cmp_mode = 0,2,4), - * the model parameters (model_value, model_of_data, rdcu_model_adr) are ignored. * @note The validity of the cfg structure is checked before the compression is * started. * diff --git a/lib/cmp_support.c b/lib/cmp_support.c index 666ce96..cfa7dc5 100644 --- a/lib/cmp_support.c +++ b/lib/cmp_support.c @@ -67,7 +67,12 @@ int is_a_pow_of_2(unsigned int v) int cmp_data_type_valid(enum cmp_data_type data_type) { - if (data_type <= DATA_TYPE_UNKOWN || data_type > DATA_TYPE_F_CAM_OFFSET) + if (data_type == DATA_TYPE_F_CAM_OFFSET) + debug_print("Error: DATA_TYPE_F_CAM_OFFSET is TBD and not implemented yet.\n"); + if (data_type == DATA_TYPE_F_CAM_BACKGROUND) + debug_print("Error: DATA_TYPE_F_CAM_BACKGROUND is TBD and not implemented yet.\n"); + + if (data_type <= DATA_TYPE_UNKNOWN || data_type > DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE) return 0; return 1; @@ -154,7 +159,7 @@ int rdcu_supported_cmp_mode_is_used(enum cmp_mode cmp_mode) /** * @brief check if the data product data type is supported by the RDCU compressor * - * @param data_type compression data product types + * @param data_type compression data product type * * @returns 1 when the data type is supported by the RDCU, otherwise 0 */ @@ -393,16 +398,18 @@ unsigned int cmp_up_model(unsigned int data, unsigned int model, /** - * @brief get the maximum valid spill threshold value for a given golomb_par + * @brief get the maximum valid spill threshold value for a RDCU HW compression + * in diff or model mode * * @param golomb_par Golomb parameter - * @param data_type compression data type * - * @returns the highest still valid spill threshold value + * @returns the highest still valid spill threshold value for a diff of model + * mode compression; 0 if golomb_par is invalid */ -uint32_t get_max_spill(unsigned int golomb_par, enum cmp_data_type data_type) +uint32_t cmp_rdcu_max_spill(unsigned int golomb_par) { + /* the RDCU can only generate 16 bit long code words -> lower max spill needed */ const uint32_t LUT_MAX_RDCU[MAX_RDCU_GOLOMB_PAR+1] = { 0, 8, 22, 35, 48, 60, 72, 84, 96, 107, 118, 129, 140, 151, 162, 173, 184, 194, 204, 214, 224, 234, 244, 254, 264, 274, 284, 294, 304, 314, 324, @@ -410,30 +417,36 @@ uint32_t get_max_spill(unsigned int golomb_par, enum cmp_data_type data_type) 452, 461, 470, 479, 488, 497, 506, 515, 524, 533, 542, 551, 560, 569, 578, 587, 596, 605, 614, 623 }; - if (golomb_par == 0) + + if (golomb_par > MAX_RDCU_GOLOMB_PAR) return 0; - /* the RDCU can only generate 16 bit long code words -> lower max spill needed */ - if (rdcu_supported_data_type_is_used(data_type)) { - if (golomb_par > MAX_RDCU_GOLOMB_PAR) - return 0; + return LUT_MAX_RDCU[golomb_par]; +} - return LUT_MAX_RDCU[golomb_par]; - } - if (golomb_par > MAX_ICU_GOLOMB_PAR) { +/** + * @brief get the maximum valid spill threshold value for a ICU SW compression + * in diff or model mode + * + * @param cmp_par compression parameter + * + * @returns the highest still valid spill threshold value for diff or model + * mode compression; 0 if the cmp_par is not valid + */ + +uint32_t cmp_icu_max_spill(unsigned int cmp_par) +{ + /* the ICU compressor can generate code words with a length of maximal 32 bits. */ + unsigned int max_cw_bits = 32; + unsigned int cutoff = (1UL << (ilog_2(cmp_par)+1)) - cmp_par; + unsigned int max_n_sym_offset = max_cw_bits/2 - 1; + + if (!cmp_par || cmp_par > MAX_ICU_GOLOMB_PAR) return 0; - } else { - /* the ICU compressor can generate code words with a length of - * maximal 32 bits. - */ - unsigned int max_cw_bits = 32; - unsigned int cutoff = (1UL << (ilog_2(golomb_par)+1)) - golomb_par; - unsigned int max_n_sym_offset = max_cw_bits/2 - 1; - - return (max_cw_bits-1-ilog_2(golomb_par))*golomb_par + cutoff - - max_n_sym_offset - 1; - } + + return (max_cw_bits-1-ilog_2(cmp_par))*cmp_par + cutoff + - max_n_sym_offset - 1; } @@ -465,13 +478,16 @@ int cmp_cfg_icu_gen_par_is_valid(const struct cmp_cfg *cfg) { int cfg_invalid = 0; + if (!cfg) + return 0; + if (!cmp_data_type_valid(cfg->data_type)) { debug_print("Error: selected compression data type is not supported.\n"); cfg_invalid++; } if (cfg->cmp_mode > CMP_MODE_STUFF) { - debug_print("Error: selected cmp_mode: %u is not supported\n.", cfg->cmp_mode); + debug_print("Error: selected cmp_mode: %u is not supported.\n", cfg->cmp_mode); cfg_invalid++; } @@ -519,14 +535,21 @@ int cmp_cfg_icu_buffers_is_valid(const struct cmp_cfg *cfg) if (cfg->samples == 0) debug_print("Warning: The samples parameter is 0. No data are compressed. This behavior may not be intended.\n"); - if (cfg->icu_output_buf && cfg->buffer_length == 0 && cfg->samples != 0) { - debug_print("Error: The buffer_length is set to 0. There is no space to store the compressed data.\n"); - cfg_invalid++; - } + if (cfg->icu_output_buf) { + if (cfg->buffer_length == 0 && cfg->samples != 0) { + debug_print("Error: The buffer_length is set to 0. There is no space to store the compressed data.\n"); + cfg_invalid++; + } - if (cfg->icu_output_buf == cfg->input_buf) { - debug_print("Error: The compressed_data buffer is the same as the data_to_compress buffer.\n"); - cfg_invalid++; + if (raw_mode_is_used(cfg->cmp_mode) && cfg->buffer_length < cfg->samples) { + debug_print("Error: The compressed_data_len_samples is to small to hold the data form the data_to_compress.\n"); + cfg_invalid++; + } + + if (cfg->icu_output_buf == cfg->input_buf) { + debug_print("Error: The compressed_data buffer is the same as the data_to_compress buffer.\n"); + cfg_invalid++; + } } if (model_mode_is_used(cfg->cmp_mode)) { @@ -558,16 +581,6 @@ int cmp_cfg_icu_buffers_is_valid(const struct cmp_cfg *cfg) } } - if (raw_mode_is_used(cfg->cmp_mode)) { - if (cfg->buffer_length < cfg->samples) { - debug_print("Error: The compressed_data_len_samples is to small to hold the data form the data_to_compress.\n"); - cfg_invalid++; - } - } else { - if (cfg->samples < cfg->buffer_length/3) - debug_print("Warning: The size of the compressed_data buffer is 3 times smaller than the data_to_compress. This is probably unintended.This is probably unintended.\n"); - } - if (cfg_invalid) return 0; @@ -589,7 +602,7 @@ int cmp_cfg_icu_buffers_is_valid(const struct cmp_cfg *cfg) */ static int cmp_pars_are_valid(uint32_t cmp_par, uint32_t spill, enum cmp_mode cmp_mode, - enum cmp_data_type data_type, char *par_name) + char *par_name) { int cfg_invalid = 0; @@ -622,9 +635,9 @@ static int cmp_pars_are_valid(uint32_t cmp_par, uint32_t spill, enum cmp_mode cm par_name, spill, MIN_ICU_SPILL); cfg_invalid++; } - if (spill > get_max_spill(cmp_par, data_type)) { + if (spill > cmp_icu_max_spill(cmp_par)) { debug_print("Error: The selected %s spillover threshold value: %u is too large for the selected %s compression parameter: %u, the largest possible spillover value in the selected compression mode is: %u.\n", - par_name, spill, par_name, cmp_par, get_max_spill(cmp_par, data_type)); + par_name, spill, par_name, cmp_par, cmp_icu_max_spill(cmp_par)); cfg_invalid++; } @@ -658,20 +671,20 @@ int cmp_cfg_imagette_is_valid(const struct cmp_cfg *cfg) return 0; if (!cmp_imagette_data_type_is_used(cfg->data_type)) { - debug_print("Error: The compression data type is not an imagette compression data type.!\n"); + debug_print("Error: The compression data type is not an imagette compression data type!\n"); cfg_invalid++; } if (!cmp_pars_are_valid(cfg->golomb_par, cfg->spill, cfg->cmp_mode, - cfg->data_type, "imagette")) + "imagette")) cfg_invalid++; if (cmp_ap_imagette_data_type_is_used(cfg->data_type)) { if (!cmp_pars_are_valid(cfg->ap1_golomb_par, cfg->ap1_spill, - cfg->cmp_mode, cfg->data_type, "adaptive 1 imagette")) + cfg->cmp_mode, "adaptive 1 imagette")) cfg_invalid++; if (!cmp_pars_are_valid(cfg->ap2_golomb_par, cfg->ap2_spill, - cfg->cmp_mode, cfg->data_type, "adaptive 2 imagette")) + cfg->cmp_mode, "adaptive 2 imagette")) cfg_invalid++; } @@ -703,7 +716,7 @@ int cmp_cfg_fx_cob_is_valid(const struct cmp_cfg *cfg) cfg_invalid++; } /* flux parameter is needed for every fx_cob data_type */ - if (!cmp_pars_are_valid(cfg->cmp_par_fx, cfg->spill_fx, cfg->cmp_mode, cfg->data_type, "flux")) + if (!cmp_pars_are_valid(cfg->cmp_par_fx, cfg->spill_fx, cfg->cmp_mode, "flux")) cfg_invalid++; switch (cfg->data_type) { @@ -763,15 +776,20 @@ int cmp_cfg_fx_cob_is_valid(const struct cmp_cfg *cfg) break; } - if (check_exp_flags && !cmp_pars_are_valid(cfg->cmp_par_exp_flags, cfg->spill_exp_flags, cfg->cmp_mode, cfg->data_type, "exposure flags")) + if (check_exp_flags && !cmp_pars_are_valid(cfg->cmp_par_exp_flags, + cfg->spill_exp_flags, cfg->cmp_mode, "exposure flags")) cfg_invalid++; - if (check_ncob && !cmp_pars_are_valid(cfg->cmp_par_ncob, cfg->spill_ncob, cfg->cmp_mode, cfg->data_type, "center of brightness")) + if (check_ncob && !cmp_pars_are_valid(cfg->cmp_par_ncob, cfg->spill_ncob, + cfg->cmp_mode, "center of brightness")) cfg_invalid++; - if (check_efx && !cmp_pars_are_valid(cfg->cmp_par_efx, cfg->spill_efx, cfg->cmp_mode, cfg->data_type, "extended flux")) + if (check_efx && !cmp_pars_are_valid(cfg->cmp_par_efx, cfg->spill_efx, + cfg->cmp_mode, "extended flux")) cfg_invalid++; - if (check_ecob && !cmp_pars_are_valid(cfg->cmp_par_ecob, cfg->spill_ecob, cfg->cmp_mode, cfg->data_type, "extended center of brightness")) + if (check_ecob && !cmp_pars_are_valid(cfg->cmp_par_ecob, cfg->spill_ecob, + cfg->cmp_mode, "extended center of brightness")) cfg_invalid++; - if (check_var && !cmp_pars_are_valid(cfg->cmp_par_fx_cob_variance, cfg->spill_fx_cob_variance, cfg->cmp_mode, cfg->data_type, "flux COB varianc")) + if (check_var && !cmp_pars_are_valid(cfg->cmp_par_fx_cob_variance, + cfg->spill_fx_cob_variance, cfg->cmp_mode, "flux COB varianc")) cfg_invalid++; if (cfg_invalid) @@ -787,6 +805,7 @@ int cmp_cfg_fx_cob_is_valid(const struct cmp_cfg *cfg) * @param cfg pointer to the compressor configuration * * @returns 1 if the auxiliary science specific parameters are valid, otherwise 0 + * TODO: implemented DATA_TYPE_F_CAM_OFFSET and DATA_TYPE_F_CAM_BACKGROUND */ int cmp_cfg_aux_is_valid(const struct cmp_cfg *cfg) @@ -801,12 +820,16 @@ int cmp_cfg_aux_is_valid(const struct cmp_cfg *cfg) cfg_invalid++; } - if (!cmp_pars_are_valid(cfg->cmp_par_mean, cfg->spill_mean, cfg->cmp_mode, cfg->data_type, "mean")) + if (!cmp_pars_are_valid(cfg->cmp_par_mean, cfg->spill_mean, + cfg->cmp_mode, "mean")) cfg_invalid++; - if (!cmp_pars_are_valid(cfg->cmp_par_variance, cfg->spill_variance, cfg->cmp_mode, cfg->data_type, "variance")) + if (!cmp_pars_are_valid(cfg->cmp_par_variance, cfg->spill_variance, + cfg->cmp_mode, "variance")) cfg_invalid++; - if (cfg->data_type != DATA_TYPE_OFFSET && cfg->data_type != DATA_TYPE_F_CAM_OFFSET) - if (!cmp_pars_are_valid(cfg->cmp_par_pixels_error, cfg->spill_pixels_error, cfg->cmp_mode, cfg->data_type, "outlier pixls num")) + /* if (cfg->data_type != DATA_TYPE_OFFSET && cfg->data_type != DATA_TYPE_F_CAM_OFFSET) */ + if (cfg->data_type != DATA_TYPE_OFFSET) + if (!cmp_pars_are_valid(cfg->cmp_par_pixels_error, cfg->spill_pixels_error, + cfg->cmp_mode, "outlier pixls num")) cfg_invalid++; if (cfg_invalid) diff --git a/test/cmp_icu/test_cmp_icu.c b/test/cmp_icu/test_cmp_icu.c index 35f938a..d2c46ce 100644 --- a/test/cmp_icu/test_cmp_icu.c +++ b/test/cmp_icu/test_cmp_icu.c @@ -1,11 +1,1151 @@ +#include "cmp_support.h" #include <string.h> #include <stdlib.h> +#if defined __has_include +# if __has_include(<time.h>) +# include <time.h> +# include <unistd.h> +# define HAS_TIME_H 1 +# endif +#endif #include "unity.h" +#include "cmp_icu.h" +#include "../lib/cmp_icu.c" /* this is a hack to test static functions */ -#include "cmp_support.h" -/* this is a hack to test static functions */ -#include "../lib/cmp_icu.c" +/* TODO: test compression with samples = 0 and buffer_length = 0; */ + +/** + * @brief Seeds the pseudo-random number generator used by rand() + */ + +void setUp(void) +{ + unsigned int seed; + static int n; + +#if HAS_TIME_H + seed = time(NULL) * getpid(); +#else + seed = 1; +#endif + + if (!n) { + n++; + srand(seed); + printf("seed: %u\n", seed); + } +} + + +/** + * @brief generate a random number + * + * @param min minimum value + * @param max maximum value + * + * @returns "random" numbers in the range [M, N] + * + * @see https://c-faq.com/lib/randrange.html + */ + +int random_range(unsigned int min, unsigned int max) +{ + if (min > max) + TEST_ASSERT(0); + if (max-min > RAND_MAX) + TEST_ASSERT(0); + return min + rand() / (RAND_MAX / (max - min + 1) + 1); +} + + +/** + * @test cmp_cfg_icu_create + */ + +void test_cmp_cfg_icu_create(void) +{ + struct cmp_cfg cfg; + enum cmp_data_type data_type; + enum cmp_mode cmp_mode; + uint32_t model_value, lossy_par; + /* TODO: change that when DATA_TYPE_BACKGROUND and + * DATA_TYPE_F_CAM_BACKGROUND are implemented */ + const enum cmp_data_type biggest_data_type = DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE; + + /* wrong data type tests */ + data_type = DATA_TYPE_UNKNOWN; /* not valid data type */ + cmp_mode = CMP_MODE_RAW; + model_value = 0; + lossy_par = CMP_LOSSLESS; + cfg = cmp_cfg_icu_create(data_type, cmp_mode, model_value, lossy_par); + TEST_ASSERT_EQUAL_INT(DATA_TYPE_UNKNOWN, cfg.data_type); + memset(&cfg, 0, sizeof(cfg)); + + data_type = biggest_data_type + 1; /* not valid data type */ + cfg = cmp_cfg_icu_create(data_type, cmp_mode, model_value, lossy_par); + TEST_ASSERT_EQUAL_INT(DATA_TYPE_UNKNOWN, cfg.data_type); + memset(&cfg, 0, sizeof(cfg)); + + data_type = biggest_data_type; + cfg = cmp_cfg_icu_create(data_type, cmp_mode, model_value, lossy_par); + TEST_ASSERT_EQUAL_INT(biggest_data_type, cfg.data_type); + TEST_ASSERT_EQUAL_INT(CMP_MODE_RAW, cfg.cmp_mode); + TEST_ASSERT_EQUAL_INT(0, cfg.model_value); + TEST_ASSERT_EQUAL_INT(0, cfg.round); + memset(&cfg, 0, sizeof(cfg)); + + /* this should work */ + data_type = DATA_TYPE_IMAGETTE; + cfg = cmp_cfg_icu_create(data_type, cmp_mode, model_value, lossy_par); + TEST_ASSERT_EQUAL_INT(DATA_TYPE_IMAGETTE, cfg.data_type); + TEST_ASSERT_EQUAL_INT(CMP_MODE_RAW, cfg.cmp_mode); + TEST_ASSERT_EQUAL_INT(0, cfg.model_value); + TEST_ASSERT_EQUAL_INT(0, cfg.round); + memset(&cfg, 0, sizeof(cfg)); + + /* wrong compression mode tests */ + cmp_mode = CMP_MODE_STUFF + 1; + cfg = cmp_cfg_icu_create(data_type, cmp_mode, model_value, lossy_par); + TEST_ASSERT_EQUAL_INT(DATA_TYPE_UNKNOWN, cfg.data_type); + memset(&cfg, 0, sizeof(cfg)); + + cmp_mode = -1; + cfg = cmp_cfg_icu_create(data_type, cmp_mode, model_value, lossy_par); + TEST_ASSERT_EQUAL_INT(DATA_TYPE_UNKNOWN, cfg.data_type); + memset(&cfg, 0, sizeof(cfg)); + + /* this should work */ + cmp_mode = CMP_MODE_STUFF; + cfg = cmp_cfg_icu_create(data_type, cmp_mode, model_value, lossy_par); + TEST_ASSERT_EQUAL_INT(DATA_TYPE_IMAGETTE, cfg.data_type); + TEST_ASSERT_EQUAL_INT(CMP_MODE_STUFF, cfg.cmp_mode); + TEST_ASSERT_EQUAL_INT(0, cfg.model_value); + TEST_ASSERT_EQUAL_INT(0, cfg.round); + memset(&cfg, 0, sizeof(cfg)); + + /* wrong model_value tests */ + cmp_mode = CMP_MODE_MODEL_MULTI; /* model value checks only active on model mode */ + model_value = MAX_MODEL_VALUE + 1; + cfg = cmp_cfg_icu_create(data_type, cmp_mode, model_value, lossy_par); + TEST_ASSERT_EQUAL_INT(DATA_TYPE_UNKNOWN, cfg.data_type); + + model_value = -1U; + cfg = cmp_cfg_icu_create(data_type, cmp_mode, model_value, lossy_par); + TEST_ASSERT_EQUAL_INT(DATA_TYPE_UNKNOWN, cfg.data_type); + + /* this should work */ + model_value = MAX_MODEL_VALUE; + cfg = cmp_cfg_icu_create(data_type, cmp_mode, model_value, lossy_par); + TEST_ASSERT_EQUAL_INT(DATA_TYPE_IMAGETTE, cfg.data_type); + TEST_ASSERT_EQUAL_INT(CMP_MODE_MODEL_MULTI, cfg.cmp_mode); + TEST_ASSERT_EQUAL_INT(16, cfg.model_value); + TEST_ASSERT_EQUAL_INT(0, cfg.round); + + /* no checks for model mode -> no model cmp_mode */ + cmp_mode = CMP_MODE_STUFF; + model_value = MAX_MODEL_VALUE + 1; + cfg = cmp_cfg_icu_create(data_type, cmp_mode, model_value, lossy_par); + TEST_ASSERT_EQUAL_INT(DATA_TYPE_IMAGETTE, cfg.data_type); + TEST_ASSERT_EQUAL_INT(CMP_MODE_STUFF, cfg.cmp_mode); + TEST_ASSERT_EQUAL_INT(MAX_MODEL_VALUE + 1, cfg.model_value); + TEST_ASSERT_EQUAL_INT(0, cfg.round); + model_value = MAX_MODEL_VALUE; + + /* wrong lossy_par tests */ + lossy_par = MAX_ICU_ROUND + 1; + cfg = cmp_cfg_icu_create(data_type, cmp_mode, model_value, lossy_par); + TEST_ASSERT_EQUAL_INT(DATA_TYPE_UNKNOWN, cfg.data_type); + + lossy_par = -1U; + cfg = cmp_cfg_icu_create(data_type, cmp_mode, model_value, lossy_par); + TEST_ASSERT_EQUAL_INT(DATA_TYPE_UNKNOWN, cfg.data_type); + + /* this should work */ + lossy_par = MAX_ICU_ROUND; + cfg = cmp_cfg_icu_create(data_type, cmp_mode, model_value, lossy_par); + TEST_ASSERT_EQUAL_INT(DATA_TYPE_IMAGETTE, cfg.data_type); + TEST_ASSERT_EQUAL_INT(CMP_MODE_STUFF, cfg.cmp_mode); + TEST_ASSERT_EQUAL_INT(16, cfg.model_value); + TEST_ASSERT_EQUAL_INT(3, cfg.round); + + /* random test */ + data_type = random_range(DATA_TYPE_IMAGETTE, biggest_data_type); + cmp_mode = random_range(CMP_MODE_RAW, CMP_MODE_STUFF); + model_value = random_range(0, MAX_MODEL_VALUE); + lossy_par = random_range(CMP_LOSSLESS, MAX_ICU_ROUND); + cfg = cmp_cfg_icu_create(data_type, cmp_mode, model_value, lossy_par); + TEST_ASSERT_EQUAL_INT(data_type, cfg.data_type); + TEST_ASSERT_EQUAL_INT(cmp_mode, cfg.cmp_mode); + TEST_ASSERT_EQUAL_INT(model_value, cfg.model_value); + TEST_ASSERT_EQUAL_INT(lossy_par, cfg.round); +} + + +/** + * @test cmp_cfg_icu_buffers + */ + +void test_cmp_cfg_icu_buffers(void) +{ + struct cmp_cfg cfg; + void *data_to_compress; + uint32_t data_samples; + void *model_of_data; + void *updated_model; + uint32_t *compressed_data; + uint32_t compressed_data_len_samples; + size_t s; + uint16_t ima_data[4] = {42, 23, 0, 0xFFFF}; + uint16_t ima_model[4] = {0xC, 0xA, 0xFF, 0xE}; + uint16_t ima_up_model[4] = {0}; + uint32_t cmp_data[2] = {0}; + + /* error case: unknown data_type */ + cfg = cmp_cfg_icu_create(DATA_TYPE_UNKNOWN, CMP_MODE_DIFF_ZERO, 16, CMP_LOSSLESS); + data_to_compress = ima_data; + data_samples = 4; + model_of_data = NULL; + updated_model = NULL; + compressed_data = cmp_data; + compressed_data_len_samples = 4; + s = cmp_cfg_icu_buffers(&cfg, data_to_compress, data_samples, + model_of_data, updated_model, compressed_data, + compressed_data_len_samples); + TEST_ASSERT_EQUAL_size_t(0, s); + + /* error case: no data test */ + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_DIFF_ZERO, 16, CMP_LOSSLESS); + data_to_compress = NULL; /* no data set */ + data_samples = 4; + model_of_data = NULL; + updated_model = NULL; + compressed_data = cmp_data; + compressed_data_len_samples = 4; + s = cmp_cfg_icu_buffers(&cfg, data_to_compress, data_samples, + model_of_data, updated_model, compressed_data, + compressed_data_len_samples); + TEST_ASSERT_EQUAL_size_t(0, s); + + /* now its should work */ + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_DIFF_ZERO, 16, CMP_LOSSLESS); + data_to_compress = ima_data; + s = cmp_cfg_icu_buffers(&cfg, data_to_compress, data_samples, + model_of_data, updated_model, compressed_data, + compressed_data_len_samples); + TEST_ASSERT_EQUAL_size_t(8, s); + TEST_ASSERT_EQUAL(ima_data, cfg.input_buf); + TEST_ASSERT_EQUAL_INT(NULL, cfg.model_buf); + TEST_ASSERT_EQUAL_INT(4, cfg.samples); + TEST_ASSERT_EQUAL(NULL, cfg.icu_new_model_buf); + TEST_ASSERT_EQUAL(cmp_data, cfg.icu_output_buf); + TEST_ASSERT_EQUAL_INT(4, cfg.buffer_length); + + /* error case: model mode and no model */ + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_MODEL_ZERO, 16, CMP_LOSSLESS); + model_of_data = NULL; + s = cmp_cfg_icu_buffers(&cfg, data_to_compress, data_samples, + model_of_data, updated_model, compressed_data, + compressed_data_len_samples); + TEST_ASSERT_EQUAL_size_t(0, s); + + /* now its should work */ + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_MODEL_ZERO, 16, CMP_LOSSLESS); + model_of_data = ima_model; + updated_model = ima_model; + s = cmp_cfg_icu_buffers(&cfg, data_to_compress, data_samples, + model_of_data, updated_model, compressed_data, + compressed_data_len_samples); + TEST_ASSERT_EQUAL_size_t(8, s); + TEST_ASSERT_EQUAL(ima_data, cfg.input_buf); + TEST_ASSERT_EQUAL_INT(ima_model, cfg.model_buf); + TEST_ASSERT_EQUAL_INT(4, cfg.samples); + TEST_ASSERT_EQUAL(ima_model, cfg.icu_new_model_buf); + TEST_ASSERT_EQUAL(cmp_data, cfg.icu_output_buf); + TEST_ASSERT_EQUAL_INT(4, cfg.buffer_length); + + /* error case: data == model */ + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_MODEL_ZERO, 16, CMP_LOSSLESS); + data_to_compress = ima_data; + model_of_data = ima_data; + s = cmp_cfg_icu_buffers(&cfg, data_to_compress, data_samples, + model_of_data, updated_model, compressed_data, + compressed_data_len_samples); + TEST_ASSERT_EQUAL_size_t(0, s); + + /* error case: data == compressed_data */ + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_MODEL_ZERO, 16, CMP_LOSSLESS); + data_to_compress = ima_data; + model_of_data = ima_model; + compressed_data = (void *)ima_data; + s = cmp_cfg_icu_buffers(&cfg, data_to_compress, data_samples, + model_of_data, updated_model, compressed_data, + compressed_data_len_samples); + TEST_ASSERT_EQUAL_size_t(0, s); + + /* error case: data == updated_model */ + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_MODEL_ZERO, 16, CMP_LOSSLESS); + data_to_compress = ima_data; + model_of_data = ima_model; + updated_model = ima_data; + compressed_data = (void *)ima_data; + s = cmp_cfg_icu_buffers(&cfg, data_to_compress, data_samples, + model_of_data, updated_model, compressed_data, + compressed_data_len_samples); + TEST_ASSERT_EQUAL_size_t(0, s); + + /* error case: model == compressed_data */ + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_MODEL_ZERO, 16, CMP_LOSSLESS); + data_to_compress = ima_data; + model_of_data = ima_model; + compressed_data = (void *)ima_model; + s = cmp_cfg_icu_buffers(&cfg, data_to_compress, data_samples, + model_of_data, updated_model, compressed_data, + compressed_data_len_samples); + TEST_ASSERT_EQUAL_size_t(0, s); + + /* error case: updated_model == compressed_data */ + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_MODEL_ZERO, 16, CMP_LOSSLESS); + data_to_compress = ima_data; + model_of_data = ima_model; + updated_model = ima_up_model; + compressed_data = (void *)ima_up_model; + s = cmp_cfg_icu_buffers(&cfg, data_to_compress, data_samples, + model_of_data, updated_model, compressed_data, + compressed_data_len_samples); + TEST_ASSERT_EQUAL_size_t(0, s); + + /* warning case: samples = 0 */ + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_MODEL_ZERO, 16, CMP_LOSSLESS); + data_to_compress = ima_data; + data_samples = 0; + model_of_data = ima_model; + updated_model = ima_up_model; + compressed_data = cmp_data; + compressed_data_len_samples = 4; + s = cmp_cfg_icu_buffers(&cfg, data_to_compress, data_samples, + model_of_data, updated_model, compressed_data, + compressed_data_len_samples); + TEST_ASSERT_EQUAL_size_t(8, s); + TEST_ASSERT_EQUAL(ima_data, cfg.input_buf); + TEST_ASSERT_EQUAL_INT(ima_model, cfg.model_buf); + TEST_ASSERT_EQUAL_INT(0, cfg.samples); + TEST_ASSERT_EQUAL(ima_up_model, cfg.icu_new_model_buf); + TEST_ASSERT_EQUAL(cmp_data, cfg.icu_output_buf); + TEST_ASSERT_EQUAL_INT(4, cfg.buffer_length); + memset(&cfg, 0, sizeof(cfg)); + + /* error case: compressed_data_len_samples = 0 */ + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_MODEL_ZERO, 16, CMP_LOSSLESS); + data_samples = 4; + compressed_data_len_samples = 0; + s = cmp_cfg_icu_buffers(&cfg, data_to_compress, data_samples, + model_of_data, updated_model, compressed_data, + compressed_data_len_samples); + TEST_ASSERT_EQUAL_size_t(0, s); + + /* this should now work */ + /* if data_samples = 0 -> compressed_data_len_samples = 0 is allowed */ + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_MODEL_ZERO, 16, CMP_LOSSLESS); + data_samples = 0; + compressed_data_len_samples = 0; + s = cmp_cfg_icu_buffers(&cfg, data_to_compress, data_samples, + model_of_data, updated_model, compressed_data, + compressed_data_len_samples); + TEST_ASSERT_EQUAL_size_t(0, s); /* not an error, it is the size of the compressed data */ + TEST_ASSERT_EQUAL(ima_data, cfg.input_buf); + TEST_ASSERT_EQUAL_INT(ima_model, cfg.model_buf); + TEST_ASSERT_EQUAL_INT(0, cfg.samples); + TEST_ASSERT_EQUAL(ima_up_model, cfg.icu_new_model_buf); + TEST_ASSERT_EQUAL(cmp_data, cfg.icu_output_buf); + TEST_ASSERT_EQUAL_INT(0, cfg.buffer_length); + + /* this should now work */ + /* if compressed_data = NULL -> compressed_data_len_samples = 0 is allowed */ + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_MODEL_ZERO, 16, CMP_LOSSLESS); + data_samples = 4; + compressed_data = NULL; + compressed_data_len_samples = 0; + s = cmp_cfg_icu_buffers(&cfg, data_to_compress, data_samples, + model_of_data, updated_model, compressed_data, + compressed_data_len_samples); + TEST_ASSERT_EQUAL_size_t(0, s); /* not an error, it is the size of the compressed data */ + TEST_ASSERT_EQUAL(ima_data, cfg.input_buf); + TEST_ASSERT_EQUAL_INT(ima_model, cfg.model_buf); + TEST_ASSERT_EQUAL_INT(4, cfg.samples); + TEST_ASSERT_EQUAL(ima_up_model, cfg.icu_new_model_buf); + TEST_ASSERT_EQUAL(NULL, cfg.icu_output_buf); + TEST_ASSERT_EQUAL_INT(0, cfg.buffer_length); + + /* error case: RAW mode compressed_data smaller than data_samples */ + compressed_data = cmp_data; + compressed_data_len_samples = 3; + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_RAW, 16, CMP_LOSSLESS); + s = cmp_cfg_icu_buffers(&cfg, data_to_compress, data_samples, + model_of_data, updated_model, compressed_data, + compressed_data_len_samples); + TEST_ASSERT_EQUAL_size_t(0, s); + + /* this should now work */ + compressed_data = NULL; + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_RAW, 16, CMP_LOSSLESS); + s = cmp_cfg_icu_buffers(&cfg, data_to_compress, data_samples, + model_of_data, updated_model, compressed_data, + compressed_data_len_samples); + TEST_ASSERT_EQUAL_size_t(6, s); + TEST_ASSERT_EQUAL(ima_data, cfg.input_buf); + TEST_ASSERT_EQUAL_INT(ima_model, cfg.model_buf); + TEST_ASSERT_EQUAL_INT(4, cfg.samples); + TEST_ASSERT_EQUAL(ima_up_model, cfg.icu_new_model_buf); + TEST_ASSERT_EQUAL(NULL, cfg.icu_output_buf); + TEST_ASSERT_EQUAL_INT(3, cfg.buffer_length); + + /* this should also now work */ + compressed_data = cmp_data; + compressed_data_len_samples = 4; + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_RAW, 16, CMP_LOSSLESS); + s = cmp_cfg_icu_buffers(&cfg, data_to_compress, data_samples, + model_of_data, updated_model, compressed_data, + compressed_data_len_samples); + TEST_ASSERT_EQUAL_size_t(8, s); + TEST_ASSERT_EQUAL(ima_data, cfg.input_buf); + TEST_ASSERT_EQUAL_INT(ima_model, cfg.model_buf); + TEST_ASSERT_EQUAL_INT(4, cfg.samples); + TEST_ASSERT_EQUAL(ima_up_model, cfg.icu_new_model_buf); + TEST_ASSERT_EQUAL(cmp_data, cfg.icu_output_buf); + TEST_ASSERT_EQUAL_INT(4, cfg.buffer_length); + + /* error case: cfg = NULL */ + s = cmp_cfg_icu_buffers(NULL, data_to_compress, data_samples, + model_of_data, updated_model, compressed_data, + compressed_data_len_samples); + TEST_ASSERT_EQUAL_size_t(0, s); +} + + +/** + * @test cmp_cfg_icu_imagette + */ + +void test_cmp_cfg_icu_imagette(void) +{ + struct cmp_cfg cfg = {0}; + uint32_t cmp_par; + uint32_t spillover_par; + enum cmp_data_type data_type; + + int error; + + /* lowest values 1d/model mode */ + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_MODEL_ZERO, 0, CMP_LOSSLESS); + cmp_par = MIN_ICU_GOLOMB_PAR; + spillover_par = MIN_ICU_SPILL; + error = cmp_cfg_icu_imagette(&cfg, cmp_par, spillover_par); + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL_INT(cfg.golomb_par, 1); + TEST_ASSERT_EQUAL_INT(cfg.spill, 2); + + /* highest values 1d/model mode */ + cfg = cmp_cfg_icu_create(DATA_TYPE_F_CAM_IMAGETTE, CMP_MODE_DIFF_MULTI, 16, CMP_LOSSLESS); + cmp_par = MAX_ICU_GOLOMB_PAR; + spillover_par = cmp_icu_max_spill(cmp_par); + error = cmp_cfg_icu_imagette(&cfg, cmp_par, spillover_par); + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL_INT(cfg.golomb_par, 0xFFFF); + TEST_ASSERT_EQUAL_INT(cfg.spill, 1048545); + + /* wrong data type test */ + for (data_type = 0; data_type <= DATA_TYPE_F_CAM_BACKGROUND; data_type++) { + cfg = cmp_cfg_icu_create(data_type, CMP_MODE_DIFF_MULTI, 16, CMP_LOSSLESS); + error = cmp_cfg_icu_imagette(&cfg, cmp_par, spillover_par); + if (data_type == DATA_TYPE_IMAGETTE || + data_type == DATA_TYPE_SAT_IMAGETTE || + data_type == DATA_TYPE_F_CAM_IMAGETTE) { + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL_INT(data_type, cfg.data_type); + TEST_ASSERT_EQUAL_INT(cfg.golomb_par, 0xFFFF); + TEST_ASSERT_EQUAL_INT(cfg.spill, 1048545); + } else { + TEST_ASSERT_TRUE(error); + } + } + + /* model/1d MODE tests */ + + /* cmp_par to big */ + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_MODEL_MULTI, 16, CMP_LOSSLESS); + cmp_par = MAX_ICU_GOLOMB_PAR + 1; + spillover_par = MIN_ICU_SPILL; + error = cmp_cfg_icu_imagette(&cfg, cmp_par, spillover_par); + TEST_ASSERT_TRUE(error); + /* ignore in RAW MODE */ + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_RAW, 16, CMP_LOSSLESS); + error = cmp_cfg_icu_imagette(&cfg, cmp_par, spillover_par); + TEST_ASSERT_FALSE(error); + + /* cmp_par to small */ + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_MODEL_MULTI, 16, CMP_LOSSLESS); + cmp_par = MIN_ICU_GOLOMB_PAR - 1; + spillover_par = MIN_ICU_SPILL; + error = cmp_cfg_icu_imagette(&cfg, cmp_par, spillover_par); + TEST_ASSERT_TRUE(error); + /* ignore in RAW MODE */ + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_RAW, 16, CMP_LOSSLESS); + error = cmp_cfg_icu_imagette(&cfg, cmp_par, spillover_par); + TEST_ASSERT_FALSE(error); + + /* spillover_par to big */ + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_MODEL_MULTI, 16, CMP_LOSSLESS); + cmp_par = MIN_ICU_GOLOMB_PAR; + spillover_par = cmp_icu_max_spill(cmp_par)+1; + error = cmp_cfg_icu_imagette(&cfg, cmp_par, spillover_par); + TEST_ASSERT_TRUE(error); + /* ignore in RAW MODE */ + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_RAW, 16, CMP_LOSSLESS); + error = cmp_cfg_icu_imagette(&cfg, cmp_par, spillover_par); + TEST_ASSERT_FALSE(error); + + /* spillover_par to small */ + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_DIFF_ZERO, 0, CMP_LOSSLESS); + cmp_par = MAX_ICU_GOLOMB_PAR; + spillover_par = MIN_ICU_SPILL -1 ; + error = cmp_cfg_icu_imagette(&cfg, cmp_par, spillover_par); + TEST_ASSERT_TRUE(error); + /* ignore in RAW MODE */ + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_RAW, 16, CMP_LOSSLESS); + error = cmp_cfg_icu_imagette(&cfg, cmp_par, spillover_par); + TEST_ASSERT_FALSE(error); + + /* CMP_MODE_STUFF tests */ + spillover_par = ~0; /* is ignored */ + + /* highest values STUFF MODE */ + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_STUFF, ~0, CMP_LOSSLESS); + cmp_par = MAX_STUFF_CMP_PAR; + error = cmp_cfg_icu_imagette(&cfg, cmp_par, spillover_par); + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL_INT(cfg.golomb_par, 32); + + /* lowest values STUFF MODE */ + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_STUFF, ~0, CMP_LOSSLESS); + cmp_par = 0; + error = cmp_cfg_icu_imagette(&cfg, cmp_par, spillover_par); + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL_INT(cfg.golomb_par, 0); + + /* cmp_par to big */ + cfg = cmp_cfg_icu_create(DATA_TYPE_SAT_IMAGETTE, CMP_MODE_STUFF, ~0, CMP_LOSSLESS); + cmp_par = MAX_STUFF_CMP_PAR+1; + error = cmp_cfg_icu_imagette(&cfg, cmp_par, spillover_par); + TEST_ASSERT_TRUE(error); + + /* cfg = NULL test*/ + error = cmp_cfg_icu_imagette(NULL, cmp_par, spillover_par); + TEST_ASSERT_TRUE(error); + + /* invalid compression mode test*/ + cfg = cmp_cfg_icu_create(DATA_TYPE_SAT_IMAGETTE, CMP_MODE_STUFF+1, ~0, CMP_LOSSLESS); + cmp_par = MAX_STUFF_CMP_PAR+1; + error = cmp_cfg_icu_imagette(&cfg, cmp_par, spillover_par); + TEST_ASSERT_TRUE(error); +} + + +/** + * @test cmp_cfg_fx_cob + */ + +void test_cmp_cfg_fx_cob(void) +{ + struct cmp_cfg cfg = {0}; + uint32_t cmp_par_exp_flags = 2; + uint32_t spillover_exp_flags = 2; + uint32_t cmp_par_fx = 2; + uint32_t spillover_fx = 2; + uint32_t cmp_par_ncob = 2; + uint32_t spillover_ncob = 2; + uint32_t cmp_par_efx = 2; + uint32_t spillover_efx = 2; + uint32_t cmp_par_ecob = 2; + uint32_t spillover_ecob = 2; + uint32_t cmp_par_fx_cob_variance = 2; + uint32_t spillover_fx_cob_variance = 2; + int error; + enum cmp_data_type data_type; + + + /* wrong data type test */ + for (data_type = 0; data_type <= DATA_TYPE_F_CAM_BACKGROUND; data_type++) { + cfg = cmp_cfg_icu_create(data_type, CMP_MODE_MODEL_ZERO, 16, CMP_LOSSLESS); + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + cmp_par_efx, spillover_efx, cmp_par_ecob, spillover_ecob, + cmp_par_fx_cob_variance, spillover_fx_cob_variance); + if (data_type == DATA_TYPE_S_FX || + data_type == DATA_TYPE_S_FX_DFX || + data_type == DATA_TYPE_S_FX_NCOB || + data_type == DATA_TYPE_S_FX_DFX_NCOB_ECOB || + data_type == DATA_TYPE_L_FX || + data_type == DATA_TYPE_L_FX_DFX || + data_type == DATA_TYPE_L_FX_NCOB || + data_type == DATA_TYPE_L_FX_DFX_NCOB_ECOB || + data_type == DATA_TYPE_F_FX || + data_type == DATA_TYPE_F_FX_DFX || + data_type == DATA_TYPE_F_FX_NCOB || + data_type == DATA_TYPE_F_FX_DFX_NCOB_ECOB) { + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL_INT(data_type, cfg.data_type); + TEST_ASSERT_EQUAL_INT(2, cfg.cmp_par_fx); + TEST_ASSERT_EQUAL_INT(2, cfg.spill_fx); + TEST_ASSERT_EQUAL_INT(2, cfg.cmp_par_exp_flags); + TEST_ASSERT_EQUAL_INT(2, cfg.spill_exp_flags); + TEST_ASSERT_EQUAL_INT(2, cfg.cmp_par_efx); + TEST_ASSERT_EQUAL_INT(2, cfg.spill_efx); + TEST_ASSERT_EQUAL_INT(2, cfg.cmp_par_ncob); + TEST_ASSERT_EQUAL_INT(2, cfg.spill_ncob); + TEST_ASSERT_EQUAL_INT(2, cfg.cmp_par_ecob); + TEST_ASSERT_EQUAL_INT(2, cfg.spill_ecob); + TEST_ASSERT_EQUAL_INT(2, cfg.cmp_par_fx_cob_variance); + TEST_ASSERT_EQUAL_INT(2, cfg.spill_fx_cob_variance); + } else { + TEST_ASSERT_TRUE(error); + } + } + + /* cfg == NULL test */ + error = cmp_cfg_fx_cob(NULL, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + cmp_par_efx, spillover_efx, cmp_par_ecob, spillover_ecob, + cmp_par_fx_cob_variance, spillover_fx_cob_variance); + TEST_ASSERT_TRUE(error); + + /* test DATA_TYPE_S_FX */ + cfg = cmp_cfg_icu_create(DATA_TYPE_S_FX, CMP_MODE_MODEL_ZERO, 16, CMP_LOSSLESS); + cmp_par_exp_flags = MAX_ICU_GOLOMB_PAR; + spillover_exp_flags = cmp_icu_max_spill(cmp_par_exp_flags); + cmp_par_fx = MIN_ICU_GOLOMB_PAR; + spillover_fx = MIN_ICU_SPILL; + cmp_par_ncob = ~0; /* invalid parameter */ + spillover_ncob = ~0; /* invalid parameter */ + cmp_par_efx = ~0; /* invalid parameter */ + spillover_efx = ~0; /* invalid parameter */ + cmp_par_ecob = ~0; /* invalid parameter */ + spillover_ecob = ~0; /* invalid parameter */ + cmp_par_fx_cob_variance = ~0; /* invalid parameter */ + spillover_fx_cob_variance = ~0; /* invalid parameter */ + + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + cmp_par_efx, spillover_efx, cmp_par_ecob, spillover_ecob, + cmp_par_fx_cob_variance, spillover_fx_cob_variance); + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL_INT(cmp_par_fx, cfg.cmp_par_fx); + TEST_ASSERT_EQUAL_INT(spillover_fx, cfg.spill_fx); + TEST_ASSERT_EQUAL_INT(cmp_par_exp_flags, cfg.cmp_par_exp_flags); + TEST_ASSERT_EQUAL_INT(spillover_exp_flags, cfg.spill_exp_flags); + + /* invalid spillover_exp_flags parameter */ + cmp_par_exp_flags = MAX_ICU_GOLOMB_PAR; + spillover_exp_flags = cmp_icu_max_spill(cmp_par_exp_flags)+1; + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + cmp_par_efx, spillover_efx, cmp_par_ecob, spillover_ecob, + cmp_par_fx_cob_variance, spillover_fx_cob_variance); + TEST_ASSERT_TRUE(error); + + /* invalid cmp_par_fx parameter */ + cmp_par_exp_flags = MAX_ICU_GOLOMB_PAR; + spillover_exp_flags = cmp_icu_max_spill(cmp_par_exp_flags); + cmp_par_fx = MIN_ICU_GOLOMB_PAR - 1; + spillover_fx = MIN_ICU_SPILL; + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + cmp_par_efx, spillover_efx, cmp_par_ecob, spillover_ecob, + cmp_par_fx_cob_variance, spillover_fx_cob_variance); + TEST_ASSERT_TRUE(error); + + + /* test DATA_TYPE_S_FX_DFX */ + cfg = cmp_cfg_icu_create(DATA_TYPE_S_FX_DFX, CMP_MODE_MODEL_ZERO, 0, 1); + cmp_par_exp_flags = MAX_ICU_GOLOMB_PAR; + spillover_exp_flags = cmp_icu_max_spill(cmp_par_exp_flags); + cmp_par_fx = MIN_ICU_GOLOMB_PAR; + spillover_fx = MIN_ICU_SPILL; + cmp_par_ncob = ~0; /* invalid parameter */ + spillover_ncob = ~0; /* invalid parameter */ + cmp_par_efx = 23; + spillover_efx = 42; + cmp_par_ecob = ~0; /* invalid parameter */ + spillover_ecob = ~0; /* invalid parameter */ + cmp_par_fx_cob_variance = ~0; /* invalid parameter */ + spillover_fx_cob_variance = ~0; /* invalid parameter */ + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + cmp_par_efx, spillover_efx, cmp_par_ecob, spillover_ecob, + cmp_par_fx_cob_variance, spillover_fx_cob_variance); + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL_INT(cmp_par_fx, cfg.cmp_par_fx); + TEST_ASSERT_EQUAL_INT(spillover_fx, cfg.spill_fx); + TEST_ASSERT_EQUAL_INT(cmp_par_exp_flags, cfg.cmp_par_exp_flags); + TEST_ASSERT_EQUAL_INT(spillover_exp_flags, cfg.spill_exp_flags); + TEST_ASSERT_EQUAL_INT(cmp_par_efx, cfg.cmp_par_efx); + TEST_ASSERT_EQUAL_INT(spillover_efx, cfg.spill_efx); + + /* invalid spillover_efx parameter */ + spillover_efx = 0; + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + cmp_par_efx, spillover_efx, cmp_par_ecob, spillover_ecob, + cmp_par_fx_cob_variance, spillover_fx_cob_variance); + TEST_ASSERT_TRUE(error); + + + /* test DATA_TYPE_S_FX_NCOB */ + cfg = cmp_cfg_icu_create(DATA_TYPE_S_FX_NCOB, CMP_MODE_MODEL_ZERO, 0, 1); + cmp_par_exp_flags = MAX_ICU_GOLOMB_PAR; + spillover_exp_flags = cmp_icu_max_spill(cmp_par_exp_flags); + cmp_par_fx = MIN_ICU_GOLOMB_PAR; + spillover_fx = MIN_ICU_SPILL; + cmp_par_ncob = 19; + spillover_ncob = 5; + cmp_par_efx = ~0; /* invalid parameter */ + spillover_efx = ~0; /* invalid parameter */ + cmp_par_ecob = ~0; /* invalid parameter */ + spillover_ecob = ~0; /* invalid parameter */ + cmp_par_fx_cob_variance = ~0; /* invalid parameter */ + spillover_fx_cob_variance = ~0; /* invalid parameter */ + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + cmp_par_efx, spillover_efx, cmp_par_ecob, spillover_ecob, + cmp_par_fx_cob_variance, spillover_fx_cob_variance); + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL_INT(cmp_par_fx, cfg.cmp_par_fx); + TEST_ASSERT_EQUAL_INT(spillover_fx, cfg.spill_fx); + TEST_ASSERT_EQUAL_INT(cmp_par_exp_flags, cfg.cmp_par_exp_flags); + TEST_ASSERT_EQUAL_INT(spillover_exp_flags, cfg.spill_exp_flags); + TEST_ASSERT_EQUAL_INT(cmp_par_ncob, cfg.cmp_par_ncob); + TEST_ASSERT_EQUAL_INT(spillover_ncob, cfg.spill_ncob); + + /* invalid cmp_par_ncob parameter */ + cmp_par_ncob = 0; + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + cmp_par_efx, spillover_efx, cmp_par_ecob, spillover_ecob, + cmp_par_fx_cob_variance, spillover_fx_cob_variance); + TEST_ASSERT_TRUE(error); + + + /* test DATA_TYPE_S_FX_DFX_NCOB_ECOB */ + cfg = cmp_cfg_icu_create(DATA_TYPE_S_FX_DFX_NCOB_ECOB, CMP_MODE_DIFF_ZERO, 7, CMP_LOSSLESS); + cmp_par_exp_flags = MAX_ICU_GOLOMB_PAR; + spillover_exp_flags = cmp_icu_max_spill(cmp_par_exp_flags); + cmp_par_fx = MIN_ICU_GOLOMB_PAR; + spillover_fx = MIN_ICU_SPILL; + cmp_par_ncob = 19; + spillover_ncob = 5; + cmp_par_efx = 23; + spillover_efx = 42; + cmp_par_ecob = MAX_ICU_GOLOMB_PAR; + spillover_ecob = MIN_ICU_SPILL; + cmp_par_fx_cob_variance = ~0; /* invalid parameter */ + spillover_fx_cob_variance = ~0; /* invalid parameter */ + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + cmp_par_efx, spillover_efx, cmp_par_ecob, spillover_ecob, + cmp_par_fx_cob_variance, spillover_fx_cob_variance); + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL_INT(cmp_par_fx, cfg.cmp_par_fx); + TEST_ASSERT_EQUAL_INT(spillover_fx, cfg.spill_fx); + TEST_ASSERT_EQUAL_INT(cmp_par_exp_flags, cfg.cmp_par_exp_flags); + TEST_ASSERT_EQUAL_INT(spillover_exp_flags, cfg.spill_exp_flags); + TEST_ASSERT_EQUAL_INT(cmp_par_ncob, cfg.cmp_par_ncob); + TEST_ASSERT_EQUAL_INT(spillover_ncob, cfg.spill_ncob); + TEST_ASSERT_EQUAL_INT(cmp_par_efx, cfg.cmp_par_efx); + TEST_ASSERT_EQUAL_INT(spillover_efx, cfg.spill_efx); + TEST_ASSERT_EQUAL_INT(cmp_par_ecob, cfg.cmp_par_ecob); + TEST_ASSERT_EQUAL_INT(spillover_ecob, cfg.spill_ecob); + + /* invalid cmp_par_ecob parameter */ + cmp_par_ecob = -1U; + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + cmp_par_efx, spillover_efx, cmp_par_ecob, spillover_ecob, + cmp_par_fx_cob_variance, spillover_fx_cob_variance); + TEST_ASSERT_TRUE(error); + + + /* DATA_TYPE_L_FX */ + cfg = cmp_cfg_icu_create(DATA_TYPE_L_FX, CMP_MODE_DIFF_ZERO, 7, CMP_LOSSLESS); + cmp_par_exp_flags = MAX_ICU_GOLOMB_PAR; + spillover_exp_flags = cmp_icu_max_spill(cmp_par_exp_flags); + cmp_par_fx = MIN_ICU_GOLOMB_PAR; + spillover_fx = MIN_ICU_SPILL; + cmp_par_ncob = ~0; /* invalid parameter */ + spillover_ncob = ~0; /* invalid parameter */ + cmp_par_efx = ~0; /* invalid parameter */ + spillover_efx = ~0; /* invalid parameter */ + cmp_par_ecob = ~0; /* invalid parameter */ + spillover_ecob = ~0; /* invalid parameter */ + cmp_par_fx_cob_variance = 30; + spillover_fx_cob_variance = 8; + + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + cmp_par_efx, spillover_efx, cmp_par_ecob, spillover_ecob, + cmp_par_fx_cob_variance, spillover_fx_cob_variance); + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL_INT(cmp_par_fx, cfg.cmp_par_fx); + TEST_ASSERT_EQUAL_INT(spillover_fx, cfg.spill_fx); + TEST_ASSERT_EQUAL_INT(cmp_par_exp_flags, cfg.cmp_par_exp_flags); + TEST_ASSERT_EQUAL_INT(spillover_exp_flags, cfg.spill_exp_flags); + TEST_ASSERT_EQUAL_INT(cmp_par_fx_cob_variance, cfg.cmp_par_fx_cob_variance); + TEST_ASSERT_EQUAL_INT(spillover_fx_cob_variance, cfg.spill_fx_cob_variance); + + /* invalid spillover_fx_cob_variance parameter */ + spillover_fx_cob_variance = 1; + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + cmp_par_efx, spillover_efx, cmp_par_ecob, spillover_ecob, + cmp_par_fx_cob_variance, spillover_fx_cob_variance); + TEST_ASSERT_TRUE(error); + + + /* DATA_TYPE_L_FX_DFX */ + cfg = cmp_cfg_icu_create(DATA_TYPE_L_FX_DFX, CMP_MODE_DIFF_ZERO, 7, CMP_LOSSLESS); + cmp_par_exp_flags = MAX_ICU_GOLOMB_PAR; + spillover_exp_flags = cmp_icu_max_spill(cmp_par_exp_flags); + cmp_par_fx = MIN_ICU_GOLOMB_PAR; + spillover_fx = MIN_ICU_SPILL; + cmp_par_ncob = ~0; /* invalid parameter */ + spillover_ncob = ~0; /* invalid parameter */ + cmp_par_efx = 23; + spillover_efx = 42; + cmp_par_ecob = ~0; /* invalid parameter */ + spillover_ecob = ~0; /* invalid parameter */ + cmp_par_fx_cob_variance = 30; + spillover_fx_cob_variance = 8; + + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + cmp_par_efx, spillover_efx, cmp_par_ecob, spillover_ecob, + cmp_par_fx_cob_variance, spillover_fx_cob_variance); + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL_INT(cmp_par_fx, cfg.cmp_par_fx); + TEST_ASSERT_EQUAL_INT(spillover_fx, cfg.spill_fx); + TEST_ASSERT_EQUAL_INT(cmp_par_exp_flags, cfg.cmp_par_exp_flags); + TEST_ASSERT_EQUAL_INT(spillover_exp_flags, cfg.spill_exp_flags); + TEST_ASSERT_EQUAL_INT(cmp_par_efx, cfg.cmp_par_efx); + TEST_ASSERT_EQUAL_INT(spillover_efx, cfg.spill_efx); + TEST_ASSERT_EQUAL_INT(cmp_par_fx_cob_variance, cfg.cmp_par_fx_cob_variance); + TEST_ASSERT_EQUAL_INT(spillover_fx_cob_variance, cfg.spill_fx_cob_variance); + + + /* DATA_TYPE_L_FX_NCOB */ + cfg = cmp_cfg_icu_create(DATA_TYPE_L_FX_NCOB, CMP_MODE_DIFF_ZERO, 7, CMP_LOSSLESS); + cmp_par_exp_flags = MAX_ICU_GOLOMB_PAR; + spillover_exp_flags = cmp_icu_max_spill(cmp_par_exp_flags); + cmp_par_fx = MIN_ICU_GOLOMB_PAR; + spillover_fx = MIN_ICU_SPILL; + cmp_par_ncob = 19; + spillover_ncob = 5; + cmp_par_efx = ~0; /* invalid parameter */ + spillover_efx = ~0; /* invalid parameter */ + cmp_par_ecob = ~0; /* invalid parameter */ + spillover_ecob = ~0; /* invalid parameter */ + cmp_par_fx_cob_variance = 30; + spillover_fx_cob_variance = 8; + + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + cmp_par_efx, spillover_efx, cmp_par_ecob, spillover_ecob, + cmp_par_fx_cob_variance, spillover_fx_cob_variance); + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL_INT(cmp_par_fx, cfg.cmp_par_fx); + TEST_ASSERT_EQUAL_INT(spillover_fx, cfg.spill_fx); + TEST_ASSERT_EQUAL_INT(cmp_par_exp_flags, cfg.cmp_par_exp_flags); + TEST_ASSERT_EQUAL_INT(spillover_exp_flags, cfg.spill_exp_flags); + TEST_ASSERT_EQUAL_INT(cmp_par_ncob, cfg.cmp_par_ncob); + TEST_ASSERT_EQUAL_INT(spillover_ncob, cfg.spill_ncob); + TEST_ASSERT_EQUAL_INT(cmp_par_fx_cob_variance, cfg.cmp_par_fx_cob_variance); + TEST_ASSERT_EQUAL_INT(spillover_fx_cob_variance, cfg.spill_fx_cob_variance); + + + /* DATA_TYPE_L_FX_DFX_NCOB_ECOB */ + cfg = cmp_cfg_icu_create(DATA_TYPE_L_FX_DFX_NCOB_ECOB, CMP_MODE_DIFF_ZERO, 7, CMP_LOSSLESS); + cmp_par_exp_flags = MAX_ICU_GOLOMB_PAR; + spillover_exp_flags = cmp_icu_max_spill(cmp_par_exp_flags); + cmp_par_fx = MIN_ICU_GOLOMB_PAR; + spillover_fx = MIN_ICU_SPILL; + cmp_par_ncob = 19; + spillover_ncob = 5; + cmp_par_efx = 23; + spillover_efx = 42; + cmp_par_ecob = MAX_ICU_GOLOMB_PAR; + spillover_ecob = MIN_ICU_SPILL; + cmp_par_fx_cob_variance = 30; + spillover_fx_cob_variance = 8; + + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + cmp_par_efx, spillover_efx, cmp_par_ecob, spillover_ecob, + cmp_par_fx_cob_variance, spillover_fx_cob_variance); + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL_INT(cmp_par_fx, cfg.cmp_par_fx); + TEST_ASSERT_EQUAL_INT(spillover_fx, cfg.spill_fx); + TEST_ASSERT_EQUAL_INT(cmp_par_exp_flags, cfg.cmp_par_exp_flags); + TEST_ASSERT_EQUAL_INT(spillover_exp_flags, cfg.spill_exp_flags); + TEST_ASSERT_EQUAL_INT(cmp_par_efx, cfg.cmp_par_efx); + TEST_ASSERT_EQUAL_INT(spillover_efx, cfg.spill_efx); + TEST_ASSERT_EQUAL_INT(cmp_par_ncob, cfg.cmp_par_ncob); + TEST_ASSERT_EQUAL_INT(spillover_ncob, cfg.spill_ncob); + TEST_ASSERT_EQUAL_INT(cmp_par_ecob, cfg.cmp_par_ecob); + TEST_ASSERT_EQUAL_INT(spillover_ecob, cfg.spill_ecob); + TEST_ASSERT_EQUAL_INT(cmp_par_fx_cob_variance, cfg.cmp_par_fx_cob_variance); + TEST_ASSERT_EQUAL_INT(spillover_fx_cob_variance, cfg.spill_fx_cob_variance); + + + /* DATA_TYPE_F_FX */ + cfg = cmp_cfg_icu_create(DATA_TYPE_F_FX, CMP_MODE_DIFF_ZERO, 7, CMP_LOSSLESS); + cmp_par_exp_flags = ~0; /* invalid parameter */ + spillover_exp_flags = ~0; /* invalid parameter */ + cmp_par_fx = MIN_ICU_GOLOMB_PAR; + spillover_fx = MIN_ICU_SPILL; + cmp_par_ncob = ~0; /* invalid parameter */ + spillover_ncob = ~0; /* invalid parameter */ + cmp_par_efx = ~0; /* invalid parameter */ + spillover_efx = ~0; /* invalid parameter */ + cmp_par_ecob = ~0; /* invalid parameter */ + spillover_ecob = ~0; /* invalid parameter */ + cmp_par_fx_cob_variance = ~0; /* invalid parameter */ + spillover_fx_cob_variance = ~0; /* invalid parameter */ + + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + cmp_par_efx, spillover_efx, cmp_par_ecob, spillover_ecob, + cmp_par_fx_cob_variance, spillover_fx_cob_variance); + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL_INT(cmp_par_fx, cfg.cmp_par_fx); + TEST_ASSERT_EQUAL_INT(spillover_fx, cfg.spill_fx); + + + /* DATA_TYPE_F_FX_DFX */ + cfg = cmp_cfg_icu_create(DATA_TYPE_F_FX_DFX, CMP_MODE_DIFF_ZERO, 7, CMP_LOSSLESS); + cmp_par_exp_flags = ~0; /* invalid parameter */ + spillover_exp_flags = ~0; /* invalid parameter */ + cmp_par_fx = MIN_ICU_GOLOMB_PAR; + spillover_fx = MIN_ICU_SPILL; + cmp_par_ncob = ~0; /* invalid parameter */ + spillover_ncob = ~0; /* invalid parameter */ + cmp_par_efx = 23; + spillover_efx = 42; + cmp_par_ecob = ~0; /* invalid parameter */ + spillover_ecob = ~0; /* invalid parameter */ + cmp_par_fx_cob_variance = ~0; /* invalid parameter */ + spillover_fx_cob_variance = ~0; /* invalid parameter */ + + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + cmp_par_efx, spillover_efx, cmp_par_ecob, spillover_ecob, + cmp_par_fx_cob_variance, spillover_fx_cob_variance); + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL_INT(cmp_par_fx, cfg.cmp_par_fx); + TEST_ASSERT_EQUAL_INT(spillover_fx, cfg.spill_fx); + TEST_ASSERT_EQUAL_INT(cmp_par_efx, cfg.cmp_par_efx); + TEST_ASSERT_EQUAL_INT(spillover_efx, cfg.spill_efx); + + + /* DATA_TYPE_F_FX_NCOB */ + cfg = cmp_cfg_icu_create(DATA_TYPE_F_FX_NCOB, CMP_MODE_DIFF_ZERO, 7, CMP_LOSSLESS); + cmp_par_exp_flags = ~0; /* invalid parameter */ + spillover_exp_flags = ~0; /* invalid parameter */ + cmp_par_fx = MIN_ICU_GOLOMB_PAR; + spillover_fx = MIN_ICU_SPILL; + cmp_par_ncob = MIN_ICU_GOLOMB_PAR; + spillover_ncob = cmp_icu_max_spill(cmp_par_ncob); + cmp_par_efx = ~0; /* invalid parameter */ + spillover_efx = ~0; /* invalid parameter */ + cmp_par_ecob = ~0; /* invalid parameter */ + spillover_ecob = ~0; /* invalid parameter */ + cmp_par_fx_cob_variance = ~0; /* invalid parameter */ + spillover_fx_cob_variance = ~0; /* invalid parameter */ + + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + cmp_par_efx, spillover_efx, cmp_par_ecob, spillover_ecob, + cmp_par_fx_cob_variance, spillover_fx_cob_variance); + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL_INT(cmp_par_fx, cfg.cmp_par_fx); + TEST_ASSERT_EQUAL_INT(spillover_fx, cfg.spill_fx); + TEST_ASSERT_EQUAL_INT(cmp_par_ncob, cfg.cmp_par_ncob); + TEST_ASSERT_EQUAL_INT(spillover_ncob, cfg.spill_ncob); + + + /* DATA_TYPE_F_FX_DFX_NCOB_ECOB */ + cfg = cmp_cfg_icu_create(DATA_TYPE_F_FX_DFX_NCOB_ECOB, CMP_MODE_DIFF_ZERO, 7, CMP_LOSSLESS); + cmp_par_exp_flags = ~0; /* invalid parameter */ + spillover_exp_flags = ~0; /* invalid parameter */ + cmp_par_fx = MIN_ICU_GOLOMB_PAR; + spillover_fx = MIN_ICU_SPILL; + cmp_par_ncob = MIN_ICU_GOLOMB_PAR; + spillover_ncob = cmp_icu_max_spill(cmp_par_ncob); + cmp_par_efx = 23; + spillover_efx = 42; + cmp_par_ecob = MAX_ICU_GOLOMB_PAR; + spillover_ecob = MIN_ICU_SPILL; + cmp_par_fx_cob_variance = ~0; /* invalid parameter */ + spillover_fx_cob_variance = ~0; /* invalid parameter */ + + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + cmp_par_efx, spillover_efx, cmp_par_ecob, spillover_ecob, + cmp_par_fx_cob_variance, spillover_fx_cob_variance); + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL_INT(cmp_par_fx, cfg.cmp_par_fx); + TEST_ASSERT_EQUAL_INT(spillover_fx, cfg.spill_fx); + TEST_ASSERT_EQUAL_INT(cmp_par_ncob, cfg.cmp_par_ncob); + TEST_ASSERT_EQUAL_INT(spillover_ncob, cfg.spill_ncob); + TEST_ASSERT_EQUAL_INT(cmp_par_efx, cfg.cmp_par_efx); + TEST_ASSERT_EQUAL_INT(spillover_efx, cfg.spill_efx); + TEST_ASSERT_EQUAL_INT(cmp_par_ecob, cfg.cmp_par_ecob); + TEST_ASSERT_EQUAL_INT(spillover_ecob, cfg.spill_ecob); +} + +/** + * @test cmp_cfg_aux + */ + +void test_cmp_cfg_aux(void) +{ struct cmp_cfg cfg; + uint32_t cmp_par_mean = 2; + uint32_t spillover_mean = 2; + uint32_t cmp_par_variance = 2; + uint32_t spillover_variance = 2; + uint32_t cmp_par_pixels_error = 2; + uint32_t spillover_pixels_error = 2; + int error; + enum cmp_data_type data_type; + + /* wrong data type test */ + for (data_type = 0; data_type <= DATA_TYPE_F_CAM_BACKGROUND; data_type++) { + cfg = cmp_cfg_icu_create(data_type, CMP_MODE_MODEL_ZERO, 16, CMP_LOSSLESS); + error = cmp_cfg_aux(&cfg, cmp_par_mean, spillover_mean, + cmp_par_variance, spillover_variance, + cmp_par_pixels_error, spillover_pixels_error); + if (data_type == DATA_TYPE_OFFSET || + data_type == DATA_TYPE_BACKGROUND || + data_type == DATA_TYPE_SMEARING + /* data_type == DATA_TYPE_F_CAM_OFFSET || */ + /* data_type == DATA_TYPE_F_CAM_BACKGROUND */ + ) { + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL_INT(data_type, cfg.data_type); + TEST_ASSERT_EQUAL_INT(2, cfg.cmp_par_mean); + TEST_ASSERT_EQUAL_INT(2, cfg.spill_mean); + TEST_ASSERT_EQUAL_INT(2, cfg.cmp_par_variance); + TEST_ASSERT_EQUAL_INT(2, cfg.spill_variance); + TEST_ASSERT_EQUAL_INT(2, cfg.cmp_par_pixels_error); + TEST_ASSERT_EQUAL_INT(2, cfg.spill_pixels_error); + } else { + TEST_ASSERT_TRUE(error); + } + } + + /* cfg == NULL test */ + error = cmp_cfg_aux(NULL, cmp_par_mean, spillover_mean, + cmp_par_variance, spillover_variance, + cmp_par_pixels_error, spillover_pixels_error); + TEST_ASSERT_TRUE(error); + + + /* DATA_TYPE_OFFSET */ + cfg = cmp_cfg_icu_create(DATA_TYPE_OFFSET, CMP_MODE_DIFF_ZERO, 7, CMP_LOSSLESS); + cmp_par_mean = MIN_ICU_GOLOMB_PAR; + spillover_mean = cmp_icu_max_spill(MIN_ICU_GOLOMB_PAR); + cmp_par_variance = MIN_ICU_GOLOMB_PAR; + spillover_variance = MIN_ICU_SPILL; + cmp_par_pixels_error = ~0; + spillover_pixels_error = ~0; + + error = cmp_cfg_aux(&cfg, cmp_par_mean, spillover_mean, + cmp_par_variance, spillover_variance, + cmp_par_pixels_error, spillover_pixels_error); + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL_INT(MIN_ICU_GOLOMB_PAR, cfg.cmp_par_mean); + TEST_ASSERT_EQUAL_INT(cmp_icu_max_spill(MIN_ICU_GOLOMB_PAR), cfg.spill_mean); + TEST_ASSERT_EQUAL_INT(MIN_ICU_GOLOMB_PAR, cfg.cmp_par_variance); + TEST_ASSERT_EQUAL_INT(2, cfg.spill_variance); + + /* This should fail */ + cmp_par_mean = MIN_ICU_GOLOMB_PAR-1; + error = cmp_cfg_aux(&cfg, cmp_par_mean, spillover_mean, + cmp_par_variance, spillover_variance, + cmp_par_pixels_error, spillover_pixels_error); + TEST_ASSERT_TRUE(error); + + + /* DATA_TYPE_BACKGROUND */ + cfg = cmp_cfg_icu_create(DATA_TYPE_BACKGROUND, CMP_MODE_DIFF_ZERO, 7, CMP_LOSSLESS); + cmp_par_mean = MAX_ICU_GOLOMB_PAR; + spillover_mean = cmp_icu_max_spill(MAX_ICU_GOLOMB_PAR); + cmp_par_variance = MIN_ICU_GOLOMB_PAR; + spillover_variance = MIN_ICU_SPILL; + cmp_par_pixels_error = 42; + spillover_pixels_error = 23; + + error = cmp_cfg_aux(&cfg, cmp_par_mean, spillover_mean, + cmp_par_variance, spillover_variance, + cmp_par_pixels_error, spillover_pixels_error); + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL_INT(MAX_ICU_GOLOMB_PAR, cfg.cmp_par_mean); + TEST_ASSERT_EQUAL_INT(cmp_icu_max_spill(MAX_ICU_GOLOMB_PAR), cfg.spill_mean); + TEST_ASSERT_EQUAL_INT(MIN_ICU_GOLOMB_PAR, cfg.cmp_par_variance); + TEST_ASSERT_EQUAL_INT(MIN_ICU_SPILL, cfg.spill_variance); + TEST_ASSERT_EQUAL_INT(42, cfg.cmp_par_pixels_error); + TEST_ASSERT_EQUAL_INT(23, cfg.spill_pixels_error); + + /* This should fail */ + cmp_par_variance = MIN_ICU_GOLOMB_PAR-1; + error = cmp_cfg_aux(&cfg, cmp_par_mean, spillover_mean, + cmp_par_variance, spillover_variance, + cmp_par_pixels_error, spillover_pixels_error); + TEST_ASSERT_TRUE(error); + + + /* DATA_TYPE_SMEARING */ + cfg = cmp_cfg_icu_create(DATA_TYPE_SMEARING, CMP_MODE_DIFF_ZERO, 7, CMP_LOSSLESS); + cmp_par_mean = MAX_ICU_GOLOMB_PAR; + spillover_mean = cmp_icu_max_spill(MAX_ICU_GOLOMB_PAR); + cmp_par_variance = MIN_ICU_GOLOMB_PAR; + spillover_variance = MIN_ICU_SPILL; + cmp_par_pixels_error = 42; + spillover_pixels_error = 23; + + error = cmp_cfg_aux(&cfg, cmp_par_mean, spillover_mean, + cmp_par_variance, spillover_variance, + cmp_par_pixels_error, spillover_pixels_error); + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL_INT(MAX_ICU_GOLOMB_PAR, cfg.cmp_par_mean); + TEST_ASSERT_EQUAL_INT(cmp_icu_max_spill(MAX_ICU_GOLOMB_PAR), cfg.spill_mean); + TEST_ASSERT_EQUAL_INT(MIN_ICU_GOLOMB_PAR, cfg.cmp_par_variance); + TEST_ASSERT_EQUAL_INT(MIN_ICU_SPILL, cfg.spill_variance); + TEST_ASSERT_EQUAL_INT(42, cfg.cmp_par_pixels_error); + TEST_ASSERT_EQUAL_INT(23, cfg.spill_pixels_error); + + /* This should fail */ + spillover_pixels_error = cmp_icu_max_spill(42)+1; + error = cmp_cfg_aux(&cfg, cmp_par_mean, spillover_mean, + cmp_par_variance, spillover_variance, + cmp_par_pixels_error, spillover_pixels_error); + TEST_ASSERT_TRUE(error); + +#if 0 +TODO: implemented F_CAM DATA_TYPE_F_CAM_OFFSET and DATA_TYPE_F_CAM_BACKGROUND + /* DATA_TYPE_F_CAM_OFFSET */ + cfg = cmp_cfg_icu_create(DATA_TYPE_F_CAM_OFFSET, CMP_MODE_DIFF_ZERO, 7, CMP_LOSSLESS); + /* DATA_TYPE_F_CAM_BACKGROUND */ + cfg = cmp_cfg_icu_create(DATA_TYPE_F_CAM_BACKGROUND, CMP_MODE_DIFF_ZERO, 7, CMP_LOSSLESS); +#endif +} /** @@ -518,6 +1658,7 @@ void test_put_n_bits32(void) void test_rice_encoder(void) { uint32_t value, g_par, log2_g_par, cw, cw_len; + const uint32_t MAX_GOLOMB_PAR = 0x80000000; /* test minimum Golomb parameter */ value = 0; log2_g_par = (uint32_t)ilog_2(MIN_ICU_GOLOMB_PAR); g_par = 1U << log2_g_par; cw = ~0U; @@ -557,7 +1698,7 @@ void test_rice_encoder(void) TEST_ASSERT_EQUAL_HEX(0xFFFFFFEF, cw); /* test maximum Golomb parameter for rice_encoder */ - value = 0; log2_g_par = (uint32_t)ilog_2(MAX_ICU_GOLOMB_PAR); g_par = 1U << log2_g_par; cw = ~0U; + value = 0; log2_g_par = (uint32_t)ilog_2(MAX_GOLOMB_PAR); g_par = 1U << log2_g_par; cw = ~0U; cw_len = rice_encoder(value, g_par, log2_g_par, &cw); TEST_ASSERT_EQUAL_INT(32, cw_len); TEST_ASSERT_EQUAL_HEX(0x0, cw); @@ -583,9 +1724,10 @@ void test_rice_encoder(void) * @test golomb_encoder */ -void test_Golomb_encoder(void) +void test_golomb_encoder(void) { uint32_t value, g_par, log2_g_par, cw, cw_len; + const uint32_t MAX_GOLOMB_PAR = 0x80000000; /* test minimum Golomb parameter */ value = 0; g_par = MIN_ICU_GOLOMB_PAR; log2_g_par = (uint32_t)ilog_2(g_par); cw = ~0U; @@ -659,12 +1801,12 @@ void test_Golomb_encoder(void) /* test maximum Golomb parameter for golomb_encoder */ - value = 0; g_par = MAX_ICU_GOLOMB_PAR; log2_g_par = (uint32_t)ilog_2(g_par); cw = ~0U; + value = 0; g_par = MAX_GOLOMB_PAR; log2_g_par = (uint32_t)ilog_2(g_par); cw = ~0U; cw_len = golomb_encoder(value, g_par, log2_g_par, &cw); TEST_ASSERT_EQUAL_INT(32, cw_len); TEST_ASSERT_EQUAL_HEX(0x0, cw); - value = 1; g_par = MAX_ICU_GOLOMB_PAR; log2_g_par = (uint32_t)ilog_2(g_par); cw = ~0U; + value = 1; g_par = MAX_GOLOMB_PAR; log2_g_par = (uint32_t)ilog_2(g_par); cw = ~0U; cw_len = golomb_encoder(value, g_par, log2_g_par, &cw); TEST_ASSERT_EQUAL_INT(32, cw_len); TEST_ASSERT_EQUAL_HEX(0x1, cw); @@ -1010,6 +2152,7 @@ void test_encode_value(void) /** * @test cmp_get_max_used_bits + * TODO: move this test */ void test_cmp_get_max_used_bits(void) @@ -1058,6 +2201,188 @@ void test_cmp_get_max_used_bits(void) } +/** + * @test configure_encoder_setup + */ + +void test_configure_encoder_setup(void) +{ + struct encoder_setupt setup; + uint32_t cmp_par; + uint32_t spillover; + uint32_t lossy_par; + uint32_t max_data_bits; + struct cmp_cfg cfg; + int error; + + /* test Golomb encoder zero escape mechanism */ + cmp_par = 42; + spillover = 23; + lossy_par = 0; + max_data_bits = 15; + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.cmp_mode = CMP_MODE_MODEL_ZERO; + cfg.icu_output_buf = (void *)123; + cfg.buffer_length = 2; + error = configure_encoder_setup(&setup, cmp_par, spillover,lossy_par, + max_data_bits, &cfg); + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL(golomb_encoder, setup.generate_cw_f); /* pointer to the code word encoder */ + TEST_ASSERT_EQUAL(encode_value_zero, setup.encode_method_f); /* pointer to the encoding function */ + TEST_ASSERT_EQUAL(123, setup.bitstream_adr); /* start address of the compressed data bitstream */ + TEST_ASSERT_EQUAL_INT(32, setup.max_stream_len); /* maximum length of the bitstream/icu_output_buf in bits */ + TEST_ASSERT_EQUAL_INT(42, setup.encoder_par1); /* encoding parameter 1 */ + TEST_ASSERT_EQUAL_INT(5, setup.encoder_par2); /* encoding parameter 2 */ + TEST_ASSERT_EQUAL_INT(23, setup.spillover_par); /* outlier parameter */ + TEST_ASSERT_EQUAL_INT(0, setup.lossy_par); /* lossy compression parameter */ + TEST_ASSERT_EQUAL_INT(15, setup.max_data_bits); /* how many bits are needed to represent the highest possible value */ + memset(&setup, 0, sizeof(setup)); + + /* test Rice encoder multi escape mechanism */ + cmp_par = 32; + spillover = 23; + lossy_par = 0; + max_data_bits = 32; + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.cmp_mode = CMP_MODE_DIFF_MULTI; + cfg.icu_output_buf = (void *)123; + cfg.buffer_length = 2; + error = configure_encoder_setup(&setup, cmp_par, spillover,lossy_par, + max_data_bits, &cfg); + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL(rice_encoder, setup.generate_cw_f); /* pointer to the code word encoder */ + TEST_ASSERT_EQUAL(encode_value_multi, setup.encode_method_f); /* pointer to the encoding function */ + TEST_ASSERT_EQUAL(123, setup.bitstream_adr); /* start address of the compressed data bitstream */ + TEST_ASSERT_EQUAL_INT(32, setup.max_stream_len); /* maximum length of the bitstream/icu_output_buf in bits */ + TEST_ASSERT_EQUAL_INT(32, setup.encoder_par1); /* encoding parameter 1 */ + TEST_ASSERT_EQUAL_INT(5, setup.encoder_par2); /* encoding parameter 2 */ + TEST_ASSERT_EQUAL_INT(23, setup.spillover_par); /* outlier parameter */ + TEST_ASSERT_EQUAL_INT(0, setup.lossy_par); /* lossy compression parameter */ + TEST_ASSERT_EQUAL_INT(32, setup.max_data_bits); /* how many bits are needed to represent the highest possible value */ + memset(&setup, 0, sizeof(setup)); + + /* test CMP_MODE_STUFF */ + cmp_par = 32; + spillover = ~0; + lossy_par = 1; + max_data_bits = 32; + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.cmp_mode = CMP_MODE_STUFF; + cfg.icu_output_buf = (void *)123; + cfg.buffer_length = 2; + error = configure_encoder_setup(&setup, cmp_par, spillover,lossy_par, + max_data_bits, &cfg); + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL(NULL, setup.generate_cw_f); /* pointer to the code word encoder */ + TEST_ASSERT_EQUAL(encode_value_none, setup.encode_method_f); /* pointer to the encoding function */ + TEST_ASSERT_EQUAL(123, setup.bitstream_adr); /* start address of the compressed data bitstream */ + TEST_ASSERT_EQUAL_INT(32, setup.max_stream_len); /* maximum length of the bitstream/icu_output_buf in bits */ + TEST_ASSERT_EQUAL_INT(32, setup.encoder_par1); /* encoding parameter 1 */ + TEST_ASSERT_EQUAL_INT(0, setup.encoder_par2); /* encoding parameter 2 */ + TEST_ASSERT_EQUAL_INT(0, setup.spillover_par); /* outlier parameter */ + TEST_ASSERT_EQUAL_INT(1, setup.lossy_par); /* lossy compression parameter */ + TEST_ASSERT_EQUAL_INT(32, setup.max_data_bits); /* how many bits are needed to represent the highest possible value */ + memset(&setup, 0, sizeof(setup)); + + /* test max_used_bits = 33 */ + cmp_par = 32; + spillover = 23; + lossy_par = 0; + max_data_bits = 33; + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.cmp_mode = CMP_MODE_DIFF_MULTI; + cfg.icu_output_buf = (void *)123; + cfg.buffer_length = 2; + error = configure_encoder_setup(&setup, cmp_par, spillover,lossy_par, + max_data_bits, &cfg); + TEST_ASSERT_TRUE(error); + memset(&setup, 0, sizeof(setup)); + + /* cmp_par = 0 test */ + cmp_par = 0; + spillover = 23; + lossy_par = 0; + max_data_bits = 32; + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.cmp_mode = CMP_MODE_DIFF_MULTI; + cfg.icu_output_buf = (void *)123; + cfg.buffer_length = 2; + error = configure_encoder_setup(&setup, cmp_par, spillover,lossy_par, + max_data_bits, &cfg); + TEST_ASSERT_TRUE(error); + memset(&setup, 0, sizeof(setup)); + + /* cmp_par = 0 test STUFF MODE this should work*/ + cmp_par = 0; + spillover = 23; + lossy_par = 0; + max_data_bits = 32; + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.cmp_mode = CMP_MODE_STUFF; + cfg.icu_output_buf = (void *)123; + cfg.buffer_length = 2; + error = configure_encoder_setup(&setup, cmp_par, spillover,lossy_par, + max_data_bits, &cfg); + TEST_ASSERT_FALSE(error); + TEST_ASSERT_EQUAL(NULL, setup.generate_cw_f); /* pointer to the code word encoder */ + TEST_ASSERT_EQUAL(encode_value_none, setup.encode_method_f); /* pointer to the encoding function */ + TEST_ASSERT_EQUAL(123, setup.bitstream_adr); /* start address of the compressed data bitstream */ + TEST_ASSERT_EQUAL_INT(32, setup.max_stream_len); /* maximum length of the bitstream/icu_output_buf in bits */ + TEST_ASSERT_EQUAL_INT(0, setup.encoder_par1); /* encoding parameter 1 */ + TEST_ASSERT_EQUAL_INT(0, setup.encoder_par2); /* encoding parameter 2 */ + TEST_ASSERT_EQUAL_INT(0, setup.spillover_par); /* outlier parameter */ + TEST_ASSERT_EQUAL_INT(0, setup.lossy_par); /* lossy compression parameter */ + TEST_ASSERT_EQUAL_INT(0, setup.max_data_bits); /* how many bits are needed to represent the highest possible value */ + memset(&setup, 0, sizeof(setup)); + + /* cmp_mode = STUFF_MODE +1 */ + cmp_par = 32; + spillover = 23; + lossy_par = 0; + max_data_bits = 1; + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.cmp_mode = CMP_MODE_STUFF+1; + cfg.icu_output_buf = (void *)123; + cfg.buffer_length = 2; + error = configure_encoder_setup(&setup, cmp_par, spillover,lossy_par, + max_data_bits, &cfg); + TEST_ASSERT_TRUE(error); + memset(&setup, 0, sizeof(setup)); + + /* setup = NULL test */ + cmp_par = 42; + spillover = 23; + lossy_par = 0; + max_data_bits = 15; + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.cmp_mode = CMP_MODE_MODEL_ZERO; + cfg.icu_output_buf = (void *)123; + cfg.buffer_length = 2; + error = configure_encoder_setup(NULL, cmp_par, spillover,lossy_par, + max_data_bits, &cfg); + TEST_ASSERT_TRUE(error); + memset(&setup, 0, sizeof(setup)); + + /* cfg = NULL test */ + cmp_par = 42; + spillover = 23; + lossy_par = 0; + max_data_bits = 15; + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.cmp_mode = CMP_MODE_MODEL_ZERO; + cfg.icu_output_buf = (void *)123; + cfg.buffer_length = 2; + error = configure_encoder_setup(&setup, cmp_par, spillover,lossy_par, + max_data_bits, NULL); + TEST_ASSERT_TRUE(error); + memset(&setup, 0, sizeof(setup)); +} + + +/** + * @test compress_imagette + */ + void test_compress_imagette_diff(void) { uint16_t data[] = {0xFFFF, 1, 0, 42, 0x8000, 0x7FFF, 0xFFFF}; @@ -1081,6 +2406,11 @@ void test_compress_imagette_diff(void) TEST_ASSERT_EQUAL_HEX(0x00000000, be32_to_cpu(output_buf[2])); } + +/** + * @test compress_imagette + */ + void test_compress_imagette_model(void) { uint16_t data[] = {0x0000, 0x0001, 0x0042, 0x8000, 0x7FFF, 0xFFFF, 0xFFFF}; @@ -1116,9 +2446,19 @@ void test_compress_imagette_model(void) TEST_ASSERT_EQUAL_HEX(0x3FFF, model_up[4]); TEST_ASSERT_EQUAL_HEX(0xFFFF, model_up[5]); TEST_ASSERT_EQUAL_HEX(0x7FFF, model_up[6]); + + + /* error case: model mode without model data */ + cfg.model_buf = NULL; /* this is the error */ + cmp_size = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL(-1, cmp_size); } +/** + * @test compress_imagette + */ + void test_compress_imagette_stuff(void) { uint16_t data[] = {0x0, 0x1, 0x23, 0x42, 0x8000, 0x7FFF, 0xFFFF}; @@ -1151,12 +2491,15 @@ void test_compress_imagette_stuff(void) } +/** + * @test compress_imagette + */ + void test_compress_imagette_raw(void) { uint16_t data[] = {0x0, 0x1, 0x23, 0x42, INT16_MIN, INT16_MAX, UINT16_MAX}; - uint16_t output_buf[7] = {0}; + uint16_t output_buf[7] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; struct cmp_cfg cfg = {0}; - int cmp_size; cfg.data_type = DATA_TYPE_IMAGETTE; @@ -1166,9 +2509,7 @@ void test_compress_imagette_raw(void) cfg.icu_output_buf = (uint32_t *)output_buf; cfg.buffer_length = 7; - cmp_size = icu_compress_data(&cfg); - TEST_ASSERT_EQUAL_INT(7*16, cmp_size); TEST_ASSERT_EQUAL_HEX16(0x0, be16_to_cpu(output_buf[0])); TEST_ASSERT_EQUAL_HEX16(0x1, be16_to_cpu(output_buf[1])); @@ -1177,6 +2518,181 @@ void test_compress_imagette_raw(void) TEST_ASSERT_EQUAL_HEX16(INT16_MIN, be16_to_cpu(output_buf[4])); TEST_ASSERT_EQUAL_HEX16(INT16_MAX, be16_to_cpu(output_buf[5])); TEST_ASSERT_EQUAL_HEX16(UINT16_MAX, be16_to_cpu(output_buf[6])); + + + /* compressed data buf = NULL test */ + memset(&cfg, 0, sizeof(struct cmp_cfg)); + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.input_buf = data; + cfg.samples = 7; + cfg.icu_output_buf = NULL; + cfg.buffer_length = 7; + + cmp_size = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(7*16, cmp_size); + + + /* error case: input_buf = NULL */ + memset(&cfg, 0, sizeof(struct cmp_cfg)); + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.input_buf = NULL; /* no data to compress */ + cfg.samples = 7; + cfg.icu_output_buf = (uint32_t *)output_buf; + cfg.buffer_length = 7; + + cmp_size = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_size); + + + /* error case: compressed data buffer to small */ + memset(&cfg, 0, sizeof(struct cmp_cfg)); + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.input_buf = data; + cfg.samples = 7; + cfg.icu_output_buf = (uint32_t *)output_buf; + cfg.buffer_length = 6; /* the buffer is to small */ + + cmp_size = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, cmp_size); +} + + +/** + * @test compress_imagette + */ + +void test_compress_imagette_error_cases(void) +{ + uint16_t data[] = {0xFFFF, 1, 0, 42, 0x8000, 0x7FFF, 0xFFFF}; + uint32_t output_buf[2] = {0xFFFF, 0xFFFF}; + struct cmp_cfg cfg = {0}; + int cmp_size; + + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.cmp_mode = CMP_MODE_DIFF_ZERO; + cfg.input_buf = NULL; + cfg.samples = 0; /* nothing to compress */ + cfg.golomb_par = 1; + cfg.spill = 8; + cfg.icu_output_buf = NULL; + cfg.buffer_length = 0; + + cmp_size = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(0, cmp_size); + + + /* compressed data buffer to small test */ + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.cmp_mode = CMP_MODE_DIFF_ZERO; + cfg.input_buf = data; + cfg.samples = 7; + cfg.golomb_par = 1; + cfg.spill = 8; + cfg.icu_output_buf = (uint32_t *)output_buf; + cfg.buffer_length = 4; + + cmp_size = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, cmp_size); + + + /* error in setup */ + struct cmp_max_used_bits max_used_bits = cmp_get_max_used_bits(); + max_used_bits.nc_imagette = 33; + cmp_set_max_used_bits(&max_used_bits); + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.cmp_mode = CMP_MODE_DIFF_ZERO; + cfg.input_buf = data; + cfg.samples = 2; + cfg.golomb_par = 1; + cfg.spill = 8; + cfg.icu_output_buf = (uint32_t *)output_buf; + cfg.buffer_length = 4; + + cmp_size = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_size); +} + + +/** + * @test compress_multi_entry_hdr + */ + +void test_compress_multi_entry_hdr(void) +{ + int stream_len; + uint8_t data[MULTI_ENTRY_HDR_SIZE]; + uint8_t model[MULTI_ENTRY_HDR_SIZE]; + uint8_t up_model[MULTI_ENTRY_HDR_SIZE]; + uint8_t cmp_data[MULTI_ENTRY_HDR_SIZE]; + uint8_t *data_p = NULL; + uint8_t *model_p = NULL; + uint8_t *up_model_p = NULL; + + memset(data, 0x42, sizeof(data)); + + /* no data; no cmp_data no model test */ + /* no data; no model; no up_model; no cmp_data */ + stream_len = compress_multi_entry_hdr((void **)&data_p, (void **)&model_p, + (void **)&up_model_p, NULL); + TEST_ASSERT_EQUAL_INT(96, stream_len); + + /* no model; no up_model */ + data_p = data; + stream_len = compress_multi_entry_hdr((void **)&data_p, (void **)&model_p, + (void **)&up_model_p, cmp_data); + TEST_ASSERT_EQUAL_INT(96, stream_len); + TEST_ASSERT_FALSE(memcmp(cmp_data, data, MULTI_ENTRY_HDR_SIZE)); + TEST_ASSERT_EQUAL(data_p-data, MULTI_ENTRY_HDR_SIZE); + + /* no up_model */ + memset(cmp_data, 0, sizeof(cmp_data)); + data_p = data; + model_p = model; + up_model_p = NULL; + stream_len = compress_multi_entry_hdr((void **)&data_p, (void **)&model_p, + (void **)&up_model_p, cmp_data); + TEST_ASSERT_EQUAL_INT(96, stream_len); + TEST_ASSERT_FALSE(memcmp(cmp_data, data, MULTI_ENTRY_HDR_SIZE)); + TEST_ASSERT_EQUAL(data_p-data, MULTI_ENTRY_HDR_SIZE); + TEST_ASSERT_EQUAL(model_p-model, MULTI_ENTRY_HDR_SIZE); + + /* all buffer test */ + memset(cmp_data, 0, sizeof(cmp_data)); + data_p = data; + model_p = model; + up_model_p = up_model; + stream_len = compress_multi_entry_hdr((void **)&data_p, (void **)&model_p, + (void **)&up_model_p, cmp_data); + TEST_ASSERT_EQUAL_INT(96, stream_len); + TEST_ASSERT_FALSE(memcmp(cmp_data, data, MULTI_ENTRY_HDR_SIZE)); + TEST_ASSERT_FALSE(memcmp(up_model, data, MULTI_ENTRY_HDR_SIZE)); + TEST_ASSERT_EQUAL(data_p-data, MULTI_ENTRY_HDR_SIZE); + TEST_ASSERT_EQUAL(model_p-model, MULTI_ENTRY_HDR_SIZE); + TEST_ASSERT_EQUAL(up_model_p-up_model, MULTI_ENTRY_HDR_SIZE); + + /* all buffer test; no cmp_data */ + memset(cmp_data, 0, sizeof(cmp_data)); + data_p = data; + model_p = model; + up_model_p = up_model; + stream_len = compress_multi_entry_hdr((void **)&data_p, (void **)&model_p, + (void **)&up_model_p, NULL); + TEST_ASSERT_EQUAL_INT(96, stream_len); + TEST_ASSERT_FALSE(memcmp(up_model, data, MULTI_ENTRY_HDR_SIZE)); + TEST_ASSERT_EQUAL(data_p-data, MULTI_ENTRY_HDR_SIZE); + TEST_ASSERT_EQUAL(model_p-model, MULTI_ENTRY_HDR_SIZE); + TEST_ASSERT_EQUAL(up_model_p-up_model, MULTI_ENTRY_HDR_SIZE); + + /* no data, use up_model test */ + memset(cmp_data, 0, sizeof(cmp_data)); + data_p = NULL; + model_p = model; + up_model_p = up_model; + stream_len = compress_multi_entry_hdr((void **)&data_p, (void **)&model_p, + (void **)&up_model_p, NULL); + TEST_ASSERT_EQUAL_INT(96, stream_len); + TEST_ASSERT_EQUAL(model_p-model, MULTI_ENTRY_HDR_SIZE); + TEST_ASSERT_EQUAL(up_model_p-up_model, MULTI_ENTRY_HDR_SIZE); } @@ -1363,9 +2879,9 @@ void test_compress_s_fx_model_multi(void) TEST_ASSERT_EQUAL_HEX(0xAFFF4DE5, be32_to_cpu(cmp_data[1])); TEST_ASSERT_EQUAL_HEX(0xCC000000, be32_to_cpu(cmp_data[2])); - TEST_ASSERT_FALSE(memcmp(cfg.input_buf, cfg.icu_output_buf, MULTI_ENTRY_HDR_SIZE)); hdr = cfg.icu_new_model_buf; up_model_buf = (struct s_fx *)hdr->entry; + TEST_ASSERT_FALSE(memcmp(hdr, cfg.icu_output_buf, MULTI_ENTRY_HDR_SIZE)); TEST_ASSERT_EQUAL_HEX(0x0, up_model_buf[0].exp_flags); TEST_ASSERT_EQUAL_HEX(0x0, up_model_buf[0].fx); TEST_ASSERT_EQUAL_HEX(0x2, up_model_buf[1].exp_flags); @@ -1386,3 +2902,139 @@ void test_compress_s_fx_model_multi(void) } +void test_compress_s_fx_efx_model_multi(void) +{ + uint32_t i; + struct s_fx_efx data[6], model[6]; + struct s_fx_efx *up_model_buf; + struct cmp_cfg cfg = {0}; + int cmp_size; + struct multi_entry_hdr *hdr; + uint32_t *cmp_data; + struct cmp_max_used_bits max_used_bits = cmp_get_max_used_bits(); + + /* define max_used_bits */ + max_used_bits.s_exp_flags = 2; + max_used_bits.s_fx = 21; + max_used_bits.s_efx = 21; + cmp_set_max_used_bits(&max_used_bits); + + /* setup configuration */ + cfg.data_type = DATA_TYPE_S_FX_DFX; + cfg.cmp_mode = CMP_MODE_MODEL_MULTI; + cfg.model_value = 16; + cfg.samples = 6; + cfg.input_buf = malloc(cmp_cal_size_of_data(cfg.samples, cfg.data_type)); + TEST_ASSERT_NOT_NULL(cfg.input_buf); + cfg.model_buf = malloc(cmp_cal_size_of_data(cfg.samples, cfg.data_type)); + TEST_ASSERT_NOT_NULL(cfg.model_buf); + cfg.icu_new_model_buf = malloc(cmp_cal_size_of_data(cfg.samples, cfg.data_type)); + TEST_ASSERT_NOT_NULL(cfg.icu_new_model_buf); + cfg.buffer_length = 6; + cfg.icu_output_buf = malloc(cmp_cal_size_of_data(cfg.buffer_length, cfg.data_type)); + TEST_ASSERT_NOT_NULL(cfg.icu_output_buf); + cfg.cmp_par_exp_flags = 1; + cfg.spill_exp_flags = 8; + cfg.cmp_par_fx = 3; + cfg.spill_fx = 35; + cfg.cmp_par_efx = 4; + cfg.spill_efx = 35; + + + /* generate input data */ + hdr = cfg.input_buf; + /* use dummy data for the header */ + memset(hdr, 0x42, sizeof(struct multi_entry_hdr)); + data[0].exp_flags = 0x0; + data[0].fx = 0x0; + data[0].efx = 0x0; + data[1].exp_flags = 0x1; + data[1].fx = 0x1; + data[1].efx = 0; + data[2].exp_flags = 0x2; + data[2].fx = 0x23; + data[2].efx = 0; + data[3].exp_flags = 0x3; + data[3].fx = 0x42; + data[3].efx = 0; + data[4].exp_flags = 0x0; + data[4].fx = 0x001FFFFF; + data[4].efx = 0; + data[5].exp_flags = 0x0; + data[5].fx = 0x0; + data[5].efx = 0; + memcpy(hdr->entry, data, sizeof(data)); + + /* generate model data */ + hdr = cfg.model_buf; + /* use dummy data for the header */ + memset(hdr, 0x41, sizeof(struct multi_entry_hdr)); + model[0].exp_flags = 0x0; + model[0].fx = 0x0; + model[0].efx = 0x1FFFFF; + model[1].exp_flags = 0x3; + model[1].fx = 0x1; + model[1].efx = 0x1FFFFF; + model[2].exp_flags = 0x0; + model[2].fx = 0x42; + model[2].efx = 0x1FFFFF; + model[3].exp_flags = 0x0; + model[3].fx = 0x23; + model[3].efx = 0x1FFFFF; + model[4].exp_flags = 0x3; + model[4].fx = 0x0; + model[4].efx = 0x1FFFFF; + model[5].exp_flags = 0x2; + model[5].fx = 0x001FFFFF; + model[5].efx = 0x1FFFFF; + memcpy(hdr->entry, model, sizeof(model)); + + cmp_size = icu_compress_data(&cfg); +#if 0 + TEST_ASSERT_EQUAL_INT(166, cmp_size); + TEST_ASSERT_FALSE(memcmp(cfg.input_buf, cfg.icu_output_buf, MULTI_ENTRY_HDR_SIZE)); + cmp_data = &cfg.icu_output_buf[MULTI_ENTRY_HDR_SIZE/sizeof(uint32_t)]; + TEST_ASSERT_EQUAL_HEX(0x1C77FFA6, be32_to_cpu(cmp_data[0])); + TEST_ASSERT_EQUAL_HEX(0xAFFF4DE5, be32_to_cpu(cmp_data[1])); + TEST_ASSERT_EQUAL_HEX(0xCC000000, be32_to_cpu(cmp_data[2])); + +#endif + hdr = cfg.icu_new_model_buf; + up_model_buf = (struct s_fx *)hdr->entry; + TEST_ASSERT_FALSE(memcmp(hdr, cfg.icu_output_buf, MULTI_ENTRY_HDR_SIZE)); + for (i = 0; i < cfg.samples; i++) { + TEST_ASSERT_EQUAL(model[i].exp_flags, up_model_buf[i].exp_flags); + TEST_ASSERT_EQUAL(model[i].fx, up_model_buf[i].fx); + TEST_ASSERT_EQUAL(model[i].efx, up_model_buf[i].efx); + } + + + free(cfg.input_buf); + free(cfg.model_buf); + free(cfg.icu_new_model_buf); + free(cfg.icu_output_buf); +} + + +/** + * @test icu_compress_data + */ + +void test_icu_compress_data_error_cases(void) +{ + int cmp_size; + struct cmp_cfg cfg = {0}; + uint16_t data[4] = {0, 0xFFFF, 23, 42}; + uint32_t cmp_data_buf[4] = {0}; + + /* cfg = NULL test */ + cmp_size = icu_compress_data(NULL); + TEST_ASSERT_EQUAL(-1, cmp_size); + + /* samples = 0 test */ + cfg.samples = 0; + cmp_size = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL(0, cmp_size); + +} + -- GitLab From a6bc7b3e211d1d4e95cc875ea2192efef4ce145a Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Thu, 1 Sep 2022 14:02:50 +0200 Subject: [PATCH 25/46] fix a bug in the model update; add a test for this bug warn if -m option is used without model compression mode read in compression parameter for non-imagette compression --- cmp_tool.c | 8 +- lib/cmp_io.c | 143 +++++++++++++++------ test/cmp_tool/cmp_tool_integration_test.py | 6 +- 3 files changed, 112 insertions(+), 45 deletions(-) diff --git a/cmp_tool.c b/cmp_tool.c index 54e20ab..0cf72ef 100644 --- a/cmp_tool.c +++ b/cmp_tool.c @@ -424,6 +424,11 @@ int main(int argc, char **argv) printf("DONE\n"); } + if (model_file_name && !guess_operation && + ((cmp_operation && !model_mode_is_used(cfg.cmp_mode)) || + (!cmp_operation && !model_mode_is_used(cmp_ent_get_cmp_mode(decomp_entity))))) + printf("Warring: Model file (-m option) specified but no model is used.\n"); + /* read in model */ if ((cmp_operation && model_mode_is_used(cfg.cmp_mode)) || (!cmp_operation && model_mode_is_used(cmp_ent_get_cmp_mode(decomp_entity))) || @@ -459,6 +464,7 @@ int main(int argc, char **argv) printf("DONE\n"); cfg.model_buf = input_model_buf; + cfg.icu_new_model_buf = input_model_buf; /* in-place model update */ } if (guess_operation) { @@ -479,7 +485,7 @@ int main(int argc, char **argv) if (!guess_operation && ((cmp_operation && model_mode_is_used(cfg.cmp_mode)) || (!cmp_operation && model_mode_is_used(cmp_ent_get_cmp_mode(decomp_entity))))) { - enum cmp_data_type data_type = DATA_TYPE_UNKOWN; + enum cmp_data_type data_type = DATA_TYPE_UNKNOWN; uint32_t model_size; printf("Write updated model to file %s_upmodel.dat ... ", output_prefix); diff --git a/lib/cmp_io.c b/lib/cmp_io.c index 9e4eb3a..83c6906 100644 --- a/lib/cmp_io.c +++ b/lib/cmp_io.c @@ -61,7 +61,7 @@ static const struct { {DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE, "DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE"}, {DATA_TYPE_F_CAM_OFFSET, "DATA_TYPE_F_CAM_OFFSET"}, {DATA_TYPE_F_CAM_BACKGROUND, "DATA_TYPE_F_CAM_BACKGROUND"}, - {DATA_TYPE_UNKOWN, "DATA_TYPE_UNKOWN"} + {DATA_TYPE_UNKNOWN, "DATA_TYPE_UNKNOWN"} }; @@ -403,12 +403,12 @@ int atoui32(const char *dep_str, const char *val_str, uint32_t *red_val) * * @param data_type_str string containing the compression data type to parse * - * @returns data type on success, DATA_TYPE_UNKOWN on error + * @returns data type on success, DATA_TYPE_UNKNOWN on error */ enum cmp_data_type string2data_type(const char *data_type_str) { - enum cmp_data_type data_type = DATA_TYPE_UNKOWN; + enum cmp_data_type data_type = DATA_TYPE_UNKNOWN; if (data_type_str) { if (isalpha(data_type_str[0])) { /* check if mode is given as text */ @@ -426,7 +426,7 @@ enum cmp_data_type string2data_type(const char *data_type_str) if (!atoui32("Compression Data Type", data_type_str, &read_val)) { data_type = read_val; if (!cmp_data_type_valid(data_type)) - data_type = DATA_TYPE_UNKOWN; + data_type = DATA_TYPE_UNKNOWN; } } } @@ -439,13 +439,13 @@ enum cmp_data_type string2data_type(const char *data_type_str) * * @param data_type compression data type to convert in string * - * @returns data type on success, DATA_TYPE_UNKOWN on error + * @returns data type on success, DATA_TYPE_UNKNOWN on error */ const char *data_type2string(enum cmp_data_type data_type) { size_t j; - const char *string = "DATA_TYPE_UNKOWN"; + const char *string = "DATA_TYPE_UNKNOWN"; for (j = 0; j < sizeof(data_type_string_table) / sizeof(data_type_string_table[0]); j++) { if (data_type == data_type_string_table[j].data_type) { @@ -527,7 +527,7 @@ static int parse_cfg(FILE *fp, struct cmp_cfg *cfg) { char *token1, *token2; char line[MAX_CONFIG_LINE]; - enum {CMP_MODE, GOLOMB_PAR, SPILL, SAMPLES, BUFFER_LENGTH, LAST_ITEM}; + enum {CMP_MODE, SAMPLES, BUFFER_LENGTH, LAST_ITEM}; int j, must_read_items[LAST_ITEM] = {0}; if (!fp) @@ -559,53 +559,24 @@ static int parse_cfg(FILE *fp, struct cmp_cfg *cfg) if (!strcmp(token1, "data_type")) { cfg->data_type = string2data_type(token2); - if (cfg->data_type == DATA_TYPE_UNKOWN) + if (cfg->data_type == DATA_TYPE_UNKNOWN) return -1; continue; } if (!strcmp(token1, "cmp_mode")) { must_read_items[CMP_MODE] = 1; - if (isalpha(*token2)) { /* check if mode is given as text or val*/ - /* TODO: use conversion function for this: */ - if (!strcmp(token2, "MODE_RAW")) { - cfg->cmp_mode = 0; - continue; - } - if (!strcmp(token2, "MODE_MODEL_ZERO")) { - cfg->cmp_mode = 1; - continue; - } - if (!strcmp(token2, "MODE_DIFF_ZERO")) { - cfg->cmp_mode = 2; - continue; - } - if (!strcmp(token2, "MODE_MODEL_MULTI")) { - cfg->cmp_mode = 3; - continue; - } - if (!strcmp(token2, "MODE_DIFF_MULTI")) { - cfg->cmp_mode = 4; - continue; - } - fprintf(stderr, "%s: Error read in cmp_mode.\n", - PROGRAM_NAME); + if (cmp_mode_parse(token2, &cfg->cmp_mode)) return -1; - } else { - if (atoui32(token1, token2, &cfg->cmp_mode)) - return -1; - continue; - } + continue; } if (!strcmp(token1, "golomb_par")) { if (atoui32(token1, token2, &cfg->golomb_par)) return -1; - must_read_items[GOLOMB_PAR] = 1; continue; } if (!strcmp(token1, "spill")) { if (atoui32(token1, token2, &cfg->spill)) return -1; - must_read_items[SPILL] = 1; continue; } if (!strcmp(token1, "model_value")) { @@ -638,6 +609,96 @@ static int parse_cfg(FILE *fp, struct cmp_cfg *cfg) return -1; continue; } + if (!strcmp(token1, "cmp_par_exp_flags")) { + if (atoui32(token1, token2, &cfg->cmp_par_exp_flags)) + return -1; + continue; + } + if (!strcmp(token1, "spill_exp_flags")) { + if (atoui32(token1, token2, &cfg->spill_exp_flags)) + return -1; + continue; + } + if (!strcmp(token1, "cmp_par_fx")) { + if (atoui32(token1, token2, &cfg->cmp_par_fx)) + return -1; + continue; + } + if (!strcmp(token1, "spill_fx")) { + if (atoui32(token1, token2, &cfg->spill_fx)) + return -1; + continue; + } + if (!strcmp(token1, "cmp_par_ncob")) { + if (atoui32(token1, token2, &cfg->cmp_par_ncob)) + return -1; + continue; + } + if (!strcmp(token1, "spill_ncob")) { + if (atoui32(token1, token2, &cfg->spill_ncob)) + return -1; + continue; + } + if (!strcmp(token1, "cmp_par_efx")) { + if (atoui32(token1, token2, &cfg->cmp_par_efx)) + return -1; + continue; + } + if (!strcmp(token1, "spill_efx")) { + if (atoui32(token1, token2, &cfg->spill_efx)) + return -1; + continue; + } + if (!strcmp(token1, "cmp_par_ecob")) { + if (atoui32(token1, token2, &cfg->cmp_par_ecob)) + return -1; + continue; + } + if (!strcmp(token1, "spill_ecob")) { + if (atoui32(token1, token2, &cfg->spill_ecob)) + return -1; + continue; + } + if (!strcmp(token1, "cmp_par_fx_cob_variance")) { + if (atoui32(token1, token2, &cfg->cmp_par_fx_cob_variance)) + return -1; + continue; + } + if (!strcmp(token1, "spill_fx_cob_variance")) { + if (atoui32(token1, token2, &cfg->spill_fx_cob_variance)) + return -1; + continue; + } + if (!strcmp(token1, "cmp_par_mean")) { + if (atoui32(token1, token2, &cfg->cmp_par_mean)) + return -1; + continue; + } + if (!strcmp(token1, "spill_mean")) { + if (atoui32(token1, token2, &cfg->spill_mean)) + return -1; + continue; + } + if (!strcmp(token1, "cmp_par_variance")) { + if (atoui32(token1, token2, &cfg->cmp_par_variance)) + return -1; + continue; + } + if (!strcmp(token1, "spill_variance")) { + if (atoui32(token1, token2, &cfg->spill_variance)) + return -1; + continue; + } + if (!strcmp(token1, "cmp_par_pixels_error")) { + if (atoui32(token1, token2, &cfg->cmp_par_pixels_error)) + return -1; + continue; + } + if (!strcmp(token1, "spill_pixels_error")) { + if (atoui32(token1, token2, &cfg->spill_pixels_error)) + return -1; + continue; + } if (!strcmp(token1, "rdcu_data_adr")) { int i = sram_addr_to_int(token2); @@ -1384,7 +1445,7 @@ ssize_t read_file_cmp_entity(const char *file_name, struct cmp_entity *ent, if (ent) { enum cmp_data_type data_type = cmp_ent_get_data_type(ent); - if (data_type == DATA_TYPE_UNKOWN) { + if (data_type == DATA_TYPE_UNKNOWN) { fprintf(stderr, "%s: %s: Error: Compression data type is not supported.\n", PROGRAM_NAME, file_name); return -1; @@ -1501,7 +1562,7 @@ static void write_cfg_internal(FILE *fp, const struct cmp_cfg *cfg, int rdcu_cfg fprintf(fp, "#-------------------------------------------------------------------------------\n"); fprintf(fp, "# Selected compression data type\n"); fprintf(fp, "\n"); - fprintf(fp, "data_type = %u\n", cfg->data_type); + fprintf(fp, "data_type = %s\n", data_type2string(cfg->data_type)); fprintf(fp, "\n"); fprintf(fp, "# Selected compression mode\n"); fprintf(fp, "# 0: raw mode\n"); diff --git a/test/cmp_tool/cmp_tool_integration_test.py b/test/cmp_tool/cmp_tool_integration_test.py index aac8b0b..f08d53b 100755 --- a/test/cmp_tool/cmp_tool_integration_test.py +++ b/test/cmp_tool/cmp_tool_integration_test.py @@ -481,7 +481,7 @@ def test_model_compression(): assert(stderr == "") cfg = parse_key_value(stdout) cfg['cmp_mode'] = 'MODE_MODEL_MULTI' - cfg['model_value'] = '16' + cfg['model_value'] = '0' cfg["samples"] = '5' cfg["buffer_length"] = '2' for key, value in cfg.items(): @@ -566,8 +566,8 @@ def test_model_compression(): with open(output_prefix1+"_upmodel.dat", encoding='utf-8') as f1: with open(output_prefix2+"_upmodel.dat", encoding='utf-8') as f2: - assert(f1.read() == f2.read() == - '00 00 00 01 00 02 00 03 00 04 \n') + assert(f1.read() == f2.read() == data) # upmodel == data -> model_value = 0 + # '00 00 00 01 00 02 00 03 00 04 \n') # clean up finally: del_file(data_file_name) -- GitLab From 4fd8c3394ba016e4a6f331c1d02985bb08c4c594 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Thu, 1 Sep 2022 14:05:43 +0200 Subject: [PATCH 26/46] fix a bug if setting the non_ima_spill2 non_ima_cmp_par2 in the cmp_ent_write_cmp_pars() functions add max_used_bits registry version to the compression entity header catch overflows in the cmp_cal_size_of_data() function --- include/cmp_data_types.h | 5 ++- include/cmp_entity.h | 4 ++- lib/cmp_data_types.c | 29 ++++++++++++--- lib/cmp_entity.c | 77 +++++++++++++++++++++++++++++++++------- test/meson.build | 1 + 5 files changed, 94 insertions(+), 22 deletions(-) diff --git a/include/cmp_data_types.h b/include/cmp_data_types.h index 9c8a470..8878557 100644 --- a/include/cmp_data_types.h +++ b/include/cmp_data_types.h @@ -84,7 +84,7 @@ /* struct holding the maximum length of the different data products types in bits */ struct cmp_max_used_bits { - unsigned int version; + uint8_t version; unsigned int s_exp_flags; unsigned int s_fx; unsigned int s_efx; @@ -127,8 +127,7 @@ struct cmp_max_used_bits { void cmp_set_max_used_bits(const struct cmp_max_used_bits *set_max_used_bits); struct cmp_max_used_bits cmp_get_max_used_bits(void); -/* for internal use only! */ -extern struct cmp_max_used_bits max_used_bits; +uint8_t cmp_get_max_used_bits_version(void); /* Source data header structure for multi entry packet */ diff --git a/include/cmp_entity.h b/include/cmp_entity.h index 0c9a606..c56cef5 100644 --- a/include/cmp_entity.h +++ b/include/cmp_entity.h @@ -116,7 +116,7 @@ struct cmp_entity { uint8_t model_value_used; /* used Model Updating Weighing Value */ uint16_t model_id; /* Model ID */ uint8_t model_counter; /* Model Counter */ - uint8_t spare; + uint8_t max_used_bits_version; uint16_t lossy_cmp_par_used; /* used Lossy Compression Parameters */ union { /* specific Compression Entity Header for the different Data Product Types */ struct imagette_header ima; @@ -174,6 +174,7 @@ int cmp_ent_set_cmp_mode(struct cmp_entity *ent, uint32_t cmp_mode_used); int cmp_ent_set_model_value(struct cmp_entity *ent, uint32_t model_value_used); int cmp_ent_set_model_id(struct cmp_entity *ent, uint32_t model_id); int cmp_ent_set_model_counter(struct cmp_entity *ent, uint32_t model_counter); +int cmp_ent_set_max_used_bits_version(struct cmp_entity *ent, uint8_t max_used_bits_version); int cmp_ent_set_lossy_cmp_par(struct cmp_entity *ent, uint32_t lossy_cmp_par_used); @@ -235,6 +236,7 @@ uint8_t cmp_ent_get_model_value_used(struct cmp_entity *ent); uint16_t cmp_ent_get_model_id(struct cmp_entity *ent); uint8_t cmp_ent_get_model_counter(struct cmp_entity *ent); +uint8_t cmp_ent_get_max_used_bits_version(struct cmp_entity *ent); uint16_t cmp_ent_get_lossy_cmp_par(struct cmp_entity *ent); diff --git a/lib/cmp_data_types.c b/lib/cmp_data_types.c index 8dd5792..5a01f8b 100644 --- a/lib/cmp_data_types.c +++ b/lib/cmp_data_types.c @@ -20,6 +20,8 @@ #include <cmp_data_types.h> #include <cmp_debug.h> #include <byteorder.h> +#include <stdint.h> +#include <stdio.h> /* the maximum length of the different data products types in bits */ @@ -88,6 +90,17 @@ struct cmp_max_used_bits cmp_get_max_used_bits(void) } +/** + * @brief get the version record form the max used bits registry + * + * @returns version of the max used bits registry + */ + +uint8_t cmp_get_max_used_bits_version(void) { + return max_used_bits.version; +} + + /** * @brief calculate the size of a sample for the different compression data type * @@ -106,6 +119,8 @@ size_t size_of_a_sample(enum cmp_data_type data_type) case DATA_TYPE_IMAGETTE_ADAPTIVE: case DATA_TYPE_SAT_IMAGETTE: case DATA_TYPE_SAT_IMAGETTE_ADAPTIVE: + case DATA_TYPE_F_CAM_IMAGETTE: + case DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE: sample_size = sizeof(uint16_t); break; case DATA_TYPE_OFFSET: @@ -155,7 +170,7 @@ size_t size_of_a_sample(enum cmp_data_type data_type) break; case DATA_TYPE_F_CAM_OFFSET: case DATA_TYPE_F_CAM_BACKGROUND: - case DATA_TYPE_UNKOWN: + case DATA_TYPE_UNKNOWN: default: debug_print("Error: Compression data type is not supported.\n"); break; @@ -178,16 +193,20 @@ size_t size_of_a_sample(enum cmp_data_type data_type) unsigned int cmp_cal_size_of_data(unsigned int samples, enum cmp_data_type data_type) { unsigned int s = size_of_a_sample(data_type); + uint64_t x; /* use 64 bit to catch overflow */ if (!s) return 0; - s *= samples; + x = (uint64_t)s*samples; if (!rdcu_supported_data_type_is_used(data_type)) - s += MULTI_ENTRY_HDR_SIZE; + x += MULTI_ENTRY_HDR_SIZE; + + if (x > UINT32_MAX) /* catch overflow */ + return 0; - return s; + return (unsigned int)x; } @@ -510,7 +529,7 @@ int cmp_input_big_to_cpu_endianness(void *data, uint32_t data_size_byte, /* TODO: implement F_CAM conversion */ case DATA_TYPE_F_CAM_OFFSET: case DATA_TYPE_F_CAM_BACKGROUND: - case DATA_TYPE_UNKOWN: + case DATA_TYPE_UNKNOWN: default: debug_print("Error: Can not swap endianness for this compression data type.\n"); return -1; diff --git a/lib/cmp_entity.c b/lib/cmp_entity.c index 0296894..6a5b040 100644 --- a/lib/cmp_entity.c +++ b/lib/cmp_entity.c @@ -97,7 +97,7 @@ uint32_t cmp_ent_cal_hdr_size(enum cmp_data_type data_type, int raw_mode_flag) case DATA_TYPE_F_CAM_BACKGROUND: size = NON_IMAGETTE_HEADER_SIZE; break; - case DATA_TYPE_UNKOWN: + case DATA_TYPE_UNKNOWN: size = 0; break; } @@ -442,6 +442,27 @@ int cmp_ent_set_model_counter(struct cmp_entity *ent, uint32_t model_counter) } +/** + * @brief set version identifier for the max. used bits registry in the + * compression entity header + * + * @param ent pointer to a compression entity + * @param max_used_bits_version the identifier for the max. used bits registry + * + * @returns 0 on success, otherwise error + */ + +int cmp_ent_set_max_used_bits_version(struct cmp_entity *ent, uint8_t max_used_bits_version) +{ + if (!ent) + return -1; + + ent->max_used_bits_version = max_used_bits_version; + + return 0; +} + + /** * @brief set the used lossy compression parameter in the compression entity * header @@ -1108,7 +1129,7 @@ uint16_t cmp_ent_get_fine_end_time(struct cmp_entity *ent) * @param ent pointer to a compression entity * * @returns the data_type NOT including the uncompressed data bit on success, - * DATA_TYPE_UNKOWN on error + * DATA_TYPE_UNKNOWN on error */ enum cmp_data_type cmp_ent_get_data_type(struct cmp_entity *ent) @@ -1116,13 +1137,13 @@ enum cmp_data_type cmp_ent_get_data_type(struct cmp_entity *ent) enum cmp_data_type data_type; if (!ent) - return DATA_TYPE_UNKOWN; + return DATA_TYPE_UNKNOWN; data_type = be16_to_cpu(ent->data_type); data_type &= (1U << RAW_BIT_DATA_TYPE_POS)-1; /* remove uncompressed data flag */ if (!cmp_data_type_valid(data_type)) - data_type = DATA_TYPE_UNKOWN; + data_type = DATA_TYPE_UNKNOWN; return data_type; } @@ -1214,6 +1235,25 @@ uint8_t cmp_ent_get_model_counter(struct cmp_entity *ent) } +/** + * @brief get the version identifier for the max. used bits registry from the + * compression entity header + * + * @param ent pointer to a compression entity + * + * @returns the version identifier for the max. used bits registry on success, + * 0 on error + */ + +uint8_t cmp_ent_get_max_use_bits_version(struct cmp_entity *ent) +{ + if (!ent) + return 0; + + return ent->max_used_bits_version; +} + + /** * @brief get the used lossy compression parameter from the compression entity header * @@ -1598,10 +1638,15 @@ void *cmp_ent_get_data_buf(struct cmp_entity *ent) if (!ent) return NULL; + data_type = cmp_ent_get_data_type(ent); + if (!cmp_data_type_valid(data_type)) { + debug_print("Error: Compression data type not supported.\n"); + return NULL; + } + if (cmp_ent_get_data_type_raw_bit(ent)) return (uint8_t *)ent + GENERIC_HEADER_SIZE; - data_type = cmp_ent_get_data_type(ent); switch (data_type) { case DATA_TYPE_IMAGETTE: @@ -1630,7 +1675,8 @@ void *cmp_ent_get_data_buf(struct cmp_entity *ent) case DATA_TYPE_F_CAM_OFFSET: case DATA_TYPE_F_CAM_BACKGROUND: return ent->non_ima.cmp_data; - case DATA_TYPE_UNKOWN: + case DATA_TYPE_UNKNOWN: + default: return NULL; } @@ -1777,6 +1823,8 @@ int cmp_ent_write_cmp_pars(struct cmp_entity *ent, const struct cmp_cfg *cfg, return -1; if (cmp_ent_set_model_value(ent, cfg->model_value)) return -1; + if (cmp_ent_set_max_used_bits_version(ent, cmp_get_max_used_bits_version())) + return -1; if (cmp_ent_set_lossy_cmp_par(ent, cfg->round)) return -1; @@ -1819,9 +1867,9 @@ int cmp_ent_write_cmp_pars(struct cmp_entity *ent, const struct cmp_cfg *cfg, if (cmp_ent_set_non_ima_spill1(ent, cfg->spill_mean)) return -1; - if (cmp_ent_set_non_ima_spill2(ent, cfg->cmp_par_variance)) + if (cmp_ent_set_non_ima_cmp_par2(ent, cfg->cmp_par_variance)) return -1; - if (cmp_ent_set_non_ima_cmp_par2(ent, cfg->spill_variance)) + if (cmp_ent_set_non_ima_spill2(ent, cfg->spill_variance)) return -1; if (cmp_ent_set_non_ima_cmp_par3(ent, cfg->cmp_par_pixels_error)) @@ -1892,7 +1940,7 @@ int cmp_ent_write_cmp_pars(struct cmp_entity *ent, const struct cmp_cfg *cfg, /* TODO: fix this*/ return -1; break; - case DATA_TYPE_UNKOWN: + case DATA_TYPE_UNKNOWN: default: return -1; } @@ -2187,7 +2235,7 @@ int cmp_ent_read_header(struct cmp_entity *ent, struct cmp_cfg *cfg) /* TODO: fix this*/ return -1; break; - case DATA_TYPE_UNKOWN: /* fall through */ + case DATA_TYPE_UNKNOWN: /* fall through */ default: return -1; } @@ -2341,8 +2389,8 @@ void cmp_ent_print(struct cmp_entity *ent) static void cmp_ent_parse_generic_header(struct cmp_entity *ent) { uint32_t version_id, cmp_ent_size, original_size, cmp_mode_used, - model_value_used, model_id, model_counter, lossy_cmp_par_used, - start_coarse_time, end_coarse_time; + model_value_used, model_id, model_counter, max_used_bits_version, + lossy_cmp_par_used, start_coarse_time, end_coarse_time; uint16_t start_fine_time, end_fine_time; enum cmp_data_type data_type; int raw_bit; @@ -2386,7 +2434,7 @@ static void cmp_ent_parse_generic_header(struct cmp_entity *ent) + ((end_fine_time - start_fine_time)/256./256.)); data_type = cmp_ent_get_data_type(ent); - if (data_type != DATA_TYPE_UNKOWN) + if (data_type != DATA_TYPE_UNKNOWN) printf("Data Product Type: %d\n", data_type); else printf("Data Product Type: unknown!"); @@ -2406,6 +2454,9 @@ static void cmp_ent_parse_generic_header(struct cmp_entity *ent) model_counter = cmp_ent_get_model_counter(ent); printf("Model Counter: %u\n", model_counter); + max_used_bits_version = cmp_ent_get_max_use_bits_version(ent); + printf("Maximum Used Bits Registry Version: %u\n", max_used_bits_version); + lossy_cmp_par_used = cmp_ent_get_lossy_cmp_par(ent); printf("Used Lossy Compression Parameters: %u\n", lossy_cmp_par_used); } diff --git a/test/meson.build b/test/meson.build index 4743653..145cdb8 100644 --- a/test/meson.build +++ b/test/meson.build @@ -37,3 +37,4 @@ subdir('cmp_tool') unity_dep = dependency('unity', fallback : ['unity', 'unity_dep']) subdir('cmp_icu') +subdir('cmp_data_types') -- GitLab From d75ff55b4ef70fd1e2e89f282564f2f010eeb8e1 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Thu, 1 Sep 2022 14:07:00 +0200 Subject: [PATCH 27/46] add decompression for non-imagette data types --- lib/decmp.c | 1917 +++++++++++++++++++++++++++++++++---- test/cmp_icu/test_decmp.c | 340 ++++++- 2 files changed, 2068 insertions(+), 189 deletions(-) diff --git a/lib/decmp.c b/lib/decmp.c index c22e5e4..3c7acfb 100644 --- a/lib/decmp.c +++ b/lib/decmp.c @@ -1,3 +1,24 @@ +/** + * @file decmp.c + * @author Dominik Loidolt (dominik.loidolt@univie.ac.at), + * @date 2020 + * + * @copyright GPLv2 + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * @brief software decompression library + * @see Data Compression User Manual PLATO-UVIE-PL-UM-0001 + * + * TODO: how to decomprss? + */ + #include <stdint.h> #include <stdio.h> #include <stdlib.h> @@ -10,19 +31,21 @@ #include "cmp_data_types.h" #include "cmp_entity.h" -#define CMP_ERROR_SAMLL_BUF -2 +/* maximum used bits registry */ +extern struct cmp_max_used_bits max_used_bits; + +/* function pointer to a code word decoder function */ +typedef int (*decoder_ptr)(unsigned int, unsigned int, unsigned int, unsigned int *); /* structure to hold a setup to encode a value */ -typedef unsigned int (*decoder_ptr)(unsigned int, unsigned int, unsigned int, unsigned int *); struct decoder_setup { - /* generate_cw_f_pt generate_cw_f; /1* pointer to the code word generation function *1/ */ - decoder_ptr decode_cw_f; - int (*encode_method_f)(uint32_t *decoded_value, int stream_pos, + decoder_ptr decode_cw_f; /* pointer to the code word decoder (Golomb/Rice)*/ + int (*decode_method_f)(uint32_t *decoded_value, int stream_pos, const struct decoder_setup *setup); /* pointer to the decoding function */ uint32_t *bitstream_adr; /* start address of the compressed data bitstream */ uint32_t max_stream_len; /* maximum length of the bitstream/icu_output_buf in bits */ - uint32_t max_cw_len; + uint32_t max_cw_len; /* TODO */ uint32_t encoder_par1; /* encoding parameter 1 */ uint32_t encoder_par2; /* encoding parameter 2 */ uint32_t outlier_par; /* outlier parameter */ @@ -32,23 +55,22 @@ struct decoder_setup { }; -double get_compression_ratio(uint32_t samples, uint32_t cmp_size_bits, - enum cmp_data_type data_type) -{ - double orign_len_bits = (double)cmp_cal_size_of_data(samples, data_type) * CHAR_BIT; - - return orign_len_bits/(double)cmp_size_bits; -} - +/** + * @brief count leading 1-bits + * + * @param value input vale + * + * @returns the number of leading 1-bits in value, starting at the most + * significant bit position + */ -static unsigned int count_leading_ones(unsigned int value) +static unsigned int count_leading_ones(uint32_t value) { unsigned int n_ones = 0; /* number of leading 1s */ while (1) { - unsigned int leading_bit; + uint32_t leading_bit = value & 0x80000000; - leading_bit = value & 0x80000000; if (!leading_bit) break; @@ -59,7 +81,19 @@ static unsigned int count_leading_ones(unsigned int value) } -static unsigned int rice_decoder(uint32_t code_word, unsigned int m, +/** + * @brief decode a Rice code word + * + * @param code_word Rice code word bitstream starting at the MSB + * @param m Golomb parameter (not used) + * @param log2_m Rice parameter, must be the same used for encoding + * @param decoded_cw pointer where decoded value is written + * + * @returns the length of the decoded code word in bits (NOT the decoded value); + * 0 on failure + */ + +static int rice_decoder(uint32_t code_word, unsigned int m, unsigned int log2_m, unsigned int *decoded_cw) { unsigned int q; /* quotient code */ @@ -68,9 +102,9 @@ static unsigned int rice_decoder(uint32_t code_word, unsigned int m, unsigned int rl; /* length of the remainder code */ unsigned int cw_len; /* length of the decoded code word in bits */ - (void)m; + (void)m; /* we don't need the Golomb parameter */ - q = count_leading_ones(code_word); + q = count_leading_ones(code_word); /* decode unary coding */ ql = q + 1; /* Number of 1's + following 0 */ rl = log2_m; @@ -96,7 +130,20 @@ static unsigned int rice_decoder(uint32_t code_word, unsigned int m, } -static unsigned int golomb_decoder(unsigned int code_word, unsigned int m, +/** + * @brief decode a Golomb code word + * + * @param code_word Golomb code word bitstream starting at the MSB + * @param m Golomb parameter (have to be bigger than 0) + * @param log2_m is log_2(m) calculate outside function for better + * performance + * @param decoded_cw pointer where decoded value is written + * + * @returns the length of the decoded code word in bits (NOT the decoded value); + * 0 on failure + */ + +static int golomb_decoder(unsigned int code_word, unsigned int m, unsigned int log2_m, unsigned int *decoded_cw) { @@ -108,7 +155,7 @@ static unsigned int golomb_decoder(unsigned int code_word, unsigned int m, unsigned int cutoff; /* cutoff between group 1 and 2 */ unsigned int cw_len; /* length of the decoded code word in bits */ - q = count_leading_ones(code_word); + q = count_leading_ones(code_word); /* decode unary coding */ rl = log2_m + 1; code_word <<= (q+1); /* shift quotient code out */ @@ -118,10 +165,10 @@ static unsigned int golomb_decoder(unsigned int code_word, unsigned int m, cutoff = (1UL << rl) - m; - if (r1 < cutoff) { + if (r1 < cutoff) { /* group 1 */ cw_len = q + rl; r = r1; - } else { + } else { /* group 2 */ cw_len = q + rl + 1; r = r2 - cutoff; } @@ -134,6 +181,16 @@ static unsigned int golomb_decoder(unsigned int code_word, unsigned int m, } +/** + * @brief select the decoder based on the used Golomb parameter + * @note if the Golomb parameter is a power of 2 we can use the faster Rice + * decoder + * + * @param golomb_par Golomb parameter (have to be bigger than 0) + * + * @returns function pointer to the select decoder function; NULL on failure + */ + static decoder_ptr select_decoder(unsigned int golomb_par) { if (!golomb_par) @@ -149,7 +206,9 @@ static decoder_ptr select_decoder(unsigned int golomb_par) /** * @brief read a value of up to 32 bits from a bitstream * - * @param p_value pointer to the read value + * @param p_value pointer to the read value (can be NULL), the + * read value will be converted to the system + * endianness * @param n_bits number of bits to read from the bitstream * @param bit_offset bit index where the bits will be read, seen from * the very beginning of the bitstream @@ -158,9 +217,10 @@ static decoder_ptr select_decoder(unsigned int golomb_par) * @param max_stream_len maximum length of the bitstream in bits; is * ignored if bitstream_adr is NULL * - * @returns length in bits of the generated bitstream on success; returns - * negative in case of erroneous input; returns CMP_ERROR_SAMLL_BUF if - * the bitstream buffer is too small to read the value from the bitstream + * @returns bit position of the last read bit in the bitstream on success; + * returns negative in case of erroneous input; returns CMP_ERROR_SAMLL_BUF + * if the bitstream buffer is too small to read the value from the + * bitstream */ static int get_n_bits32(uint32_t *p_value, unsigned int n_bits, int bit_offset, @@ -174,16 +234,14 @@ static int get_n_bits32(uint32_t *p_value, unsigned int n_bits, int bit_offset, /*leave in case of erroneous input */ if (bit_offset < 0) return -1; - if (n_bits == 0) - return stream_len; - + return -1; if (n_bits > 32) return -1; + /* nothing to read */ if (!bitstream_adr) return stream_len; - if (!p_value) return stream_len; @@ -231,88 +289,130 @@ static int get_n_bits32(uint32_t *p_value, unsigned int n_bits, int bit_offset, } -static int decode_normal(uint32_t *decoded_value, int stream_pos, const struct decoder_setup *setup) -{ - uint32_t read_val = ~0U; - int n_read_bits, cw_len, n_bits; +/** + * @brief decode a Golomb/Rice encoded code word from the bitstream + * + * @param decoded_value pointer to the decoded value + * @param stream_pos start bit position code word to be decoded in the bitstream + * @param setup pointer to the decoder setup + * + * @returns bit index of the next code word in the bitstream on success; returns + * negative in case of erroneous input; returns CMP_ERROR_SAMLL_BUF if the + * bitstream buffer is too small to read the value from the bitstream + */ - if (stream_pos + setup->max_cw_len > setup->max_stream_len) /* check buffer overflow */ - n_read_bits = setup->max_stream_len - stream_pos; +static int decode_normal(uint32_t *decoded_value, int stream_pos, + const struct decoder_setup *setup) +{ + uint32_t read_val; + unsigned int n_read_bits; + int stream_pos_read, cw_len; + + /* check if we can read max_cw_len or less; we do not now how long the + * code word actually is so we try to read the maximum cw length */ + if ((unsigned int)stream_pos + setup->max_cw_len > setup->max_stream_len) + n_read_bits = setup->max_stream_len - (unsigned int)stream_pos; else n_read_bits = setup->max_cw_len; - if (n_read_bits >= 32 || n_read_bits == 0) - return -1; - n_bits = get_n_bits32(&read_val, n_read_bits, stream_pos, - setup->bitstream_adr, setup->max_stream_len); - if (n_bits <= 0) - return -1; + stream_pos_read = get_n_bits32(&read_val, n_read_bits, stream_pos, + setup->bitstream_adr, setup->max_stream_len); + if (stream_pos_read < 0) + return stream_pos_read; + /* if we read less than 32, we shift the bitstream so that it starts at the MSB */ read_val = read_val << (32 - n_read_bits); - cw_len = setup->decode_cw_f(read_val, setup->encoder_par1, setup->encoder_par2, decoded_value); - if (cw_len < 0) + cw_len = setup->decode_cw_f(read_val, setup->encoder_par1, + setup->encoder_par2, decoded_value); + if (cw_len <= 0) + return -1; + /* consistency check: code word length can not be bigger than read bits */ + if (cw_len > (int)n_read_bits) return -1; return stream_pos + cw_len; } -static int decode_multi(uint32_t *decoded_value, int stream_pos, - const struct decoder_setup *setup) +/** + * @brief decode a Golomb/Rice encoded code word with zero escape system + * mechanism from the bitstream + * + * @param decoded_value pointer to the decoded value + * @param stream_pos start bit position code word to be decoded in the bitstream + * @param setup pointer to the decoder setup + * + * @returns bit index of the next code word in the bitstream on success; returns + * negative in case of erroneous input; returns CMP_ERROR_SAMLL_BUF if the + * bitstream buffer is too small to read the value from the bitstream + */ + +static int decode_zero(uint32_t *decoded_value, int stream_pos, + const struct decoder_setup *setup) { stream_pos = decode_normal(decoded_value, stream_pos, setup); if (stream_pos < 0) return stream_pos; - if (*decoded_value >= setup->outlier_par) { - /* escape symbol mechanism was used; read unencoded value */ - int n_bits; - uint32_t unencoded_val = 0; - unsigned int unencoded_len; + /* consistency check: value lager than the outlier parameter should not + * be Golomb/Rice encoded */ + if (*decoded_value > setup->outlier_par) + return -1; - unencoded_len = (*decoded_value - setup->outlier_par + 1) * 2; + if (*decoded_value == 0) { + /* escape symbol mechanism was used; read unencoded value */ + uint32_t unencoded_val; - n_bits = get_n_bits32(&unencoded_val, unencoded_len, stream_pos, - setup->bitstream_adr, setup->max_stream_len); - if (n_bits <= 0) + stream_pos = get_n_bits32(&unencoded_val, setup->max_data_bits, stream_pos, + setup->bitstream_adr, setup->max_stream_len); + if (stream_pos < 0) + return stream_pos; + /* consistency check: outliers must be bigger than the outlier_par */ + if (unencoded_val < setup->outlier_par && unencoded_val != 0) return -1; - *decoded_value = unencoded_val + setup->outlier_par; - stream_pos += unencoded_len; + *decoded_value = unencoded_val; } + + (*decoded_value)--; + if (*decoded_value == 0xFFFFFFFF) /* catch underflow */ + (*decoded_value) >>= (32 - setup->max_data_bits); + return stream_pos; } -static int decode_zero(uint32_t *decoded_value, int stream_pos, - const struct decoder_setup *setup) +/** + * @brief decode a Golomb/Rice encoded code word with multi escape system + * mechanism from the bitstream + * + * @param decoded_value pointer to the decoded value + * @param stream_pos start bit position code word to be decoded in the bitstream + * @param setup pointer to the decoder setup + * + * @returns bit index of the next code word in the bitstream on success; returns + * negative in case of erroneous input; returns CMP_ERROR_SAMLL_BUF if the + * bitstream buffer is too small to read the value from the bitstream + */ + +static int decode_multi(uint32_t *decoded_value, int stream_pos, + const struct decoder_setup *setup) { stream_pos = decode_normal(decoded_value, stream_pos, setup); - if (stream_pos <= 0) - return stream_pos; - - if (*decoded_value > setup->outlier_par) /* consistency check */ - return -1; + if (*decoded_value >= setup->outlier_par) { + /* escape symbol mechanism was used; read unencoded value */ + uint32_t unencoded_val; + unsigned int unencoded_len; - if (*decoded_value == 0) {/* escape symbol mechanism was used; read unencoded value */ - int n_bits; - uint32_t unencoded_val = 0; + unencoded_len = (*decoded_value - setup->outlier_par + 1) * 2; - n_bits = get_n_bits32(&unencoded_val, setup->max_data_bits, stream_pos, - setup->bitstream_adr, setup->max_stream_len); - if (n_bits <= 0) - return -1; - if (unencoded_val < setup->outlier_par && unencoded_val != 0) /* consistency check */ - return -1; + stream_pos = get_n_bits32(&unencoded_val, unencoded_len, stream_pos, + setup->bitstream_adr, setup->max_stream_len); - *decoded_value = unencoded_val; - stream_pos += setup->max_data_bits; + *decoded_value = unencoded_val + setup->outlier_par; } - (*decoded_value)--; - if (*decoded_value == 0xFFFFFFFF) /* catch underflow */ - (*decoded_value) = (*decoded_value) >> (32-setup->max_data_bits); return stream_pos; } @@ -332,21 +432,36 @@ static uint32_t re_map_to_pos(uint32_t value_to_unmap) if (value_to_unmap == 0xFFFFFFFF) /* catch overflow */ return 0x80000000; return -((value_to_unmap + 1) / 2); - } else + } else { return value_to_unmap / 2; + } } +/** + * @brief decompress the next code word in the bitstream and decorate it with + * the model + * + * @param decoded_value pointer to the decoded value + * @param model model of the decoded_value (0 if not used) + * @param stream_pos start bit position code word to be decoded in the bitstream + * @param setup pointer to the decoder setup + * + * @returns bit index of the next code word in the bitstream on success; returns + * negative in case of erroneous input; returns CMP_ERROR_SAMLL_BUF if the + * bitstream buffer is too small to read the value from the bitstream + */ + static int decode_value(uint32_t *decoded_value, uint32_t model, int stream_pos, const struct decoder_setup *setup) { uint32_t mask = (~0U >> (32 - setup->max_data_bits)); /* mask the used bits */ - stream_pos = setup->encode_method_f(decoded_value, stream_pos, setup); + stream_pos = setup->decode_method_f(decoded_value, stream_pos, setup); if (stream_pos <= 0) return stream_pos; - *decoded_value = re_map_to_pos(*decoded_value); //, setup->max_used_bits); + *decoded_value = re_map_to_pos(*decoded_value); *decoded_value += round_fwd(model, setup->lossy_par); @@ -364,9 +479,9 @@ static int configure_decoder_setup(struct decoder_setup *setup, const struct cmp_cfg *cfg) { if (multi_escape_mech_is_used(cfg->cmp_mode)) - setup->encode_method_f = &decode_multi; + setup->decode_method_f = &decode_multi; else if (zero_escape_mech_is_used(cfg->cmp_mode)) - setup->encode_method_f = &decode_zero; + setup->decode_method_f = &decode_zero; else { debug_print("Error: Compression mode not supported.\n"); return -1; @@ -396,6 +511,7 @@ static int configure_decoder_setup(struct decoder_setup *setup, static int decompress_imagette(struct cmp_cfg *cfg) { + int err; size_t i; int stream_pos = 0; struct decoder_setup setup; @@ -404,10 +520,9 @@ static int decompress_imagette(struct cmp_cfg *cfg) uint16_t *up_model_buf = cfg->icu_new_model_buf; uint32_t decoded_value = 0; uint16_t model; - int err; err = configure_decoder_setup(&setup, cfg->golomb_par, cfg->spill, - cfg->round, 16, cfg); + cfg->round, max_used_bits.nc_imagette, cfg); if (err) return -1; @@ -432,118 +547,1588 @@ static int decompress_imagette(struct cmp_cfg *cfg) return stream_pos; } -static int decompressed_data_internal(struct cmp_cfg *cfg) -{ - int data_size, strem_len_bit = -1; - if (!cfg) - return 0; /* or -1? */ - if (!cfg->icu_output_buf) +/** + * @brief TODO: decompress the multi-entry packet header structure and sets the data, + * model and up_model pointers to the data after the header + * + * @param data pointer to a pointer pointing to the data to be compressed + * @param model pointer to a pointer pointing to the model of the data + * @param up_model pointer to a pointer pointing to the updated model buffer + * @param cfg pointer to the compression configuration structure + * + * @returns the bit length of the bitstream on success; negative on error, + * + * @note the (void **) cast relies on all pointer types having the same internal + * representation which is common, but not universal; http://www.c-faq.com/ptrs/genericpp.html + */ + +static int decompress_multi_entry_hdr(void **data, void **model, void **up_model, + const struct cmp_cfg *cfg) +{ + if (cfg->buffer_length < MULTI_ENTRY_HDR_SIZE) return -1; - data_size = cmp_cal_size_of_data(cfg->samples, cfg->data_type); - if (!cfg->input_buf || !data_size) - return data_size; + if (*data) { + if (cfg->icu_output_buf) + memcpy(*data, cfg->icu_output_buf, MULTI_ENTRY_HDR_SIZE); + *data = (uint8_t *)*data + MULTI_ENTRY_HDR_SIZE; + } - if (model_mode_is_used(cfg->cmp_mode)) - if (!cfg->model_buf) - return -1; + if (*model) { + if (cfg->icu_output_buf) + memcpy(*model, cfg->icu_output_buf, MULTI_ENTRY_HDR_SIZE); + *model = (uint8_t *)*model + MULTI_ENTRY_HDR_SIZE; + } - if (cfg->cmp_mode == CMP_MODE_RAW) { + if (*up_model) { + if (cfg->icu_output_buf) + memcpy(*up_model, cfg->icu_output_buf, MULTI_ENTRY_HDR_SIZE); + *up_model = (uint8_t *)*up_model + MULTI_ENTRY_HDR_SIZE; + } - if ((unsigned int)data_size < cfg->buffer_length/CHAR_BIT) - return -1; + return MULTI_ENTRY_HDR_SIZE * CHAR_BIT; +} - if (cfg->input_buf) { - memcpy(cfg->input_buf, cfg->icu_output_buf, data_size); - if (cmp_input_big_to_cpu_endianness(cfg->input_buf, data_size, cfg->data_type)) - return -1; - strem_len_bit = data_size * CHAR_BIT; - } - } else { - switch (cfg->data_type) { - case DATA_TYPE_IMAGETTE: - strem_len_bit = decompress_imagette(cfg); - break; - default: - strem_len_bit = -1; - debug_print("Error: Compressed data type not supported.\n"); - break; - } +/** + * @brief decompress short normal light flux (S_FX) data + * + * @param cfg pointer to the compression configuration structure + * + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream + */ + +static int decompress_s_fx(const struct cmp_cfg *cfg) +{ + size_t i; + int stream_pos = 0; + uint32_t decoded_value; + struct decoder_setup setup_exp_flags, setup_fx; + struct s_fx *data_buf = cfg->input_buf; + struct s_fx *model_buf = cfg->model_buf; + struct s_fx *up_model_buf = NULL; + struct s_fx *next_model_p; + struct s_fx model; + + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; + stream_pos = decompress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); + + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; } - /* TODO: is this usefull? if (strem_len_bit != data_size * CHAR_BIT) { */ - if (strem_len_bit <= 0) + + if (configure_decoder_setup(&setup_exp_flags, cfg->cmp_par_exp_flags, cfg->spill_exp_flags, + cfg->round, max_used_bits.s_exp_flags, cfg)) + return -1; + if (configure_decoder_setup(&setup_fx, cfg->cmp_par_fx, cfg->spill_fx, + cfg->round, max_used_bits.s_fx, cfg)) return -1; + for (i = 0; ; i++) { + stream_pos = decode_value(&decoded_value, model.exp_flags, + stream_pos, &setup_exp_flags); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].exp_flags = decoded_value; + + stream_pos = decode_value(&decoded_value, model.fx, stream_pos, + &setup_fx); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].fx = decoded_value; - return data_size; + if (up_model_buf) { + up_model_buf[i].exp_flags = cmp_up_model(data_buf[i].exp_flags, model.exp_flags, + cfg->model_value, setup_exp_flags.lossy_par); + up_model_buf[i].fx = cmp_up_model(data_buf[i].fx, model.fx, + cfg->model_value, setup_fx.lossy_par); + } + + if (i >= cfg->samples-1) + break; + + model = next_model_p[i]; + } + return stream_pos; } -int decompress_cmp_entiy(struct cmp_entity *ent, void *model_buf, - void *up_model_buf, void *decompressed_data) +/** + * @brief decompress S_FX_DFX data + * + * @param cfg pointer to the compression configuration structure + * + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream + */ + +static int decompress_s_fx_efx(const struct cmp_cfg *cfg) { - int err; - struct cmp_cfg cfg = {0}; + size_t i; + int stream_pos = 0; + uint32_t decoded_value; + struct decoder_setup setup_exp_flags, setup_fx, setup_efx; + struct s_fx_efx *data_buf = cfg->input_buf; + struct s_fx_efx *model_buf = cfg->model_buf; + struct s_fx_efx *up_model_buf = NULL; + struct s_fx_efx *next_model_p; + struct s_fx_efx model; - cfg.model_buf = model_buf; - cfg.icu_new_model_buf = up_model_buf; - cfg.input_buf = decompressed_data; + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; - if (!ent) - return -1; + stream_pos = decompress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); - err = cmp_ent_read_header(ent, &cfg); - if (err) + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } + + if (configure_decoder_setup(&setup_exp_flags, cfg->cmp_par_exp_flags, cfg->spill_exp_flags, + cfg->round, max_used_bits.s_exp_flags, cfg)) + return -1; + if (configure_decoder_setup(&setup_fx, cfg->cmp_par_fx, cfg->spill_fx, + cfg->round, max_used_bits.s_fx, cfg)) + return -1; + if (configure_decoder_setup(&setup_efx, cfg->cmp_par_efx, cfg->spill_efx, + cfg->round, max_used_bits.s_efx, cfg)) return -1; - return decompressed_data_internal(&cfg); + for (i = 0; ; i++) { + stream_pos = decode_value(&decoded_value, model.exp_flags, + stream_pos, &setup_exp_flags); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].exp_flags = decoded_value; + + stream_pos = decode_value(&decoded_value, model.fx, stream_pos, + &setup_fx); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].fx = decoded_value; + + stream_pos = decode_value(&decoded_value, model.efx, stream_pos, + &setup_efx); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].efx = decoded_value; + + if (up_model_buf) { + up_model_buf[i].exp_flags = cmp_up_model(data_buf[i].exp_flags, model.exp_flags, + cfg->model_value, setup_exp_flags.lossy_par); + up_model_buf[i].fx = cmp_up_model(data_buf[i].fx, model.fx, + cfg->model_value, setup_fx.lossy_par); + up_model_buf[i].efx = cmp_up_model(data_buf[i].efx, model.efx, + cfg->model_value, setup_efx.lossy_par); + } + + if (i >= cfg->samples-1) + break; + + model = next_model_p[i]; + } + return stream_pos; } -/* model buffer is overwritten with updated model */ +/** + * @brief decompress short S_FX_NCOB data + * + * @param cfg pointer to the compression configuration structure + * + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream + */ -int decompress_data(uint32_t *compressed_data, void *de_model_buf, - const struct cmp_info *info, void *decompressed_data) +static int decompress_s_fx_ncob(const struct cmp_cfg *cfg) { - int size_decomp_data; - struct cmp_cfg cfg = {0}; - - if (!compressed_data) - return -1; + size_t i; + int stream_pos = 0; + uint32_t decoded_value; + struct decoder_setup setup_exp_flags, setup_fx, setup_ncob; + struct s_fx_ncob *data_buf = cfg->input_buf; + struct s_fx_ncob *model_buf = cfg->model_buf; + struct s_fx_ncob *up_model_buf = NULL; + struct s_fx_ncob *next_model_p; + struct s_fx_ncob model; - if (!info) - return -1; + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; - if (info->cmp_err) - return -1; + stream_pos = decompress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); - if (model_mode_is_used(info->cmp_mode_used)) - if (!de_model_buf) - return -1; - /* TODO: add ohter modes */ + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } - if (!decompressed_data) + if (configure_decoder_setup(&setup_exp_flags, cfg->cmp_par_exp_flags, cfg->spill_exp_flags, + cfg->round, max_used_bits.s_exp_flags, cfg)) return -1; - - /* cfg.data_type = info->data_type_used; */ - cfg.cmp_mode = info->cmp_mode_used; - cfg.model_value = info->model_value_used; - cfg.round = info->round_used; - cfg.spill = info->spill_used; - cfg.golomb_par = info->golomb_par_used; - cfg.samples = info->samples_used; - cfg.icu_output_buf = compressed_data; - cfg.buffer_length = cmp_bit_to_4byte(info->cmp_size); - cfg.input_buf = decompressed_data; - cfg.model_buf = de_model_buf; - size_decomp_data = decompressed_data_internal(&cfg); - if (size_decomp_data <= 0) + if (configure_decoder_setup(&setup_fx, cfg->cmp_par_fx, cfg->spill_fx, + cfg->round, max_used_bits.s_fx, cfg)) return -1; - else - return 0; + if (configure_decoder_setup(&setup_ncob, cfg->cmp_par_ncob, cfg->spill_ncob, + cfg->round, max_used_bits.s_ncob, cfg)) + return -1; + + for (i = 0; ; i++) { + stream_pos = decode_value(&decoded_value, model.exp_flags, + stream_pos, &setup_exp_flags); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].exp_flags = decoded_value; + stream_pos = decode_value(&decoded_value, model.fx, stream_pos, + &setup_fx); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].fx = decoded_value; + stream_pos = decode_value(&decoded_value, model.ncob_x, stream_pos, + &setup_ncob); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].ncob_x = decoded_value; + + stream_pos = decode_value(&decoded_value, model.ncob_y, stream_pos, + &setup_ncob); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].ncob_y = decoded_value; + + if (up_model_buf) { + up_model_buf[i].exp_flags = cmp_up_model(data_buf[i].exp_flags, model.exp_flags, + cfg->model_value, setup_exp_flags.lossy_par); + up_model_buf[i].fx = cmp_up_model(data_buf[i].fx, model.fx, + cfg->model_value, setup_fx.lossy_par); + up_model_buf[i].ncob_x = cmp_up_model(data_buf[i].ncob_x, model.ncob_x, + cfg->model_value, setup_ncob.lossy_par); + up_model_buf[i].ncob_y = cmp_up_model(data_buf[i].ncob_y, model.ncob_y, + cfg->model_value, setup_ncob.lossy_par); + } + + if (i >= cfg->samples-1) + break; + + model = next_model_p[i]; + } + return stream_pos; +} + + +/** + * @brief decompress short S_FX_NCOB_ECOB data + * + * @param cfg pointer to the compression configuration structure + * + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream + */ + +static int decompress_s_fx_efx_ncob_ecob(const struct cmp_cfg *cfg) +{ + size_t i; + int stream_pos = 0; + uint32_t decoded_value; + struct decoder_setup setup_exp_flags, setup_fx, setup_ncob, setup_efx, setup_ecob; + struct s_fx_efx_ncob_ecob *data_buf = cfg->input_buf; + struct s_fx_efx_ncob_ecob *model_buf = cfg->model_buf; + struct s_fx_efx_ncob_ecob *up_model_buf = NULL; + struct s_fx_efx_ncob_ecob *next_model_p; + struct s_fx_efx_ncob_ecob model; + + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; + + stream_pos = decompress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); + + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } + + if (configure_decoder_setup(&setup_exp_flags, cfg->cmp_par_exp_flags, cfg->spill_exp_flags, + cfg->round, max_used_bits.s_exp_flags, cfg)) + return -1; + if (configure_decoder_setup(&setup_fx, cfg->cmp_par_fx, cfg->spill_fx, + cfg->round, max_used_bits.s_fx, cfg)) + return -1; + if (configure_decoder_setup(&setup_ncob, cfg->cmp_par_ncob, cfg->spill_ncob, + cfg->round, max_used_bits.s_ncob, cfg)) + return -1; + if (configure_decoder_setup(&setup_efx, cfg->cmp_par_efx, cfg->spill_efx, + cfg->round, max_used_bits.s_efx, cfg)) + return -1; + if (configure_decoder_setup(&setup_ecob, cfg->cmp_par_ecob, cfg->spill_ecob, + cfg->round, max_used_bits.s_ecob, cfg)) + return -1; + + for (i = 0; ; i++) { + stream_pos = decode_value(&decoded_value, model.exp_flags, + stream_pos, &setup_exp_flags); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].exp_flags = decoded_value; + + stream_pos = decode_value(&decoded_value, model.fx, stream_pos, + &setup_fx); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].fx = decoded_value; + + stream_pos = decode_value(&decoded_value, model.ncob_x, stream_pos, + &setup_ncob); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].ncob_x = decoded_value; + + stream_pos = decode_value(&decoded_value, model.ncob_y, stream_pos, + &setup_ncob); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].ncob_y = decoded_value; + + stream_pos = decode_value(&decoded_value, model.efx, stream_pos, + &setup_efx); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].efx = decoded_value; + + stream_pos = decode_value(&decoded_value, model.ecob_x, stream_pos, + &setup_ecob); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].ecob_x = decoded_value; + + stream_pos = decode_value(&decoded_value, model.ecob_y, stream_pos, + &setup_ecob); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].ecob_y = decoded_value; + + if (up_model_buf) { + up_model_buf[i].exp_flags = cmp_up_model(data_buf[i].exp_flags, model.exp_flags, + cfg->model_value, setup_exp_flags.lossy_par); + up_model_buf[i].fx = cmp_up_model(data_buf[i].fx, model.fx, + cfg->model_value, setup_fx.lossy_par); + up_model_buf[i].ncob_x = cmp_up_model(data_buf[i].ncob_x, model.ncob_x, + cfg->model_value, setup_ncob.lossy_par); + up_model_buf[i].ncob_y = cmp_up_model(data_buf[i].ncob_y, model.ncob_y, + cfg->model_value, setup_ncob.lossy_par); + up_model_buf[i].efx = cmp_up_model(data_buf[i].efx, model.efx, + cfg->model_value, setup_efx.lossy_par); + up_model_buf[i].ecob_x = cmp_up_model(data_buf[i].ecob_x, model.ecob_x, + cfg->model_value, setup_ecob.lossy_par); + up_model_buf[i].ecob_y = cmp_up_model(data_buf[i].ecob_y, model.ecob_y, + cfg->model_value, setup_ecob.lossy_par); + } + + if (i >= cfg->samples-1) + break; + + model = next_model_p[i]; + } + return stream_pos; +} + + +/** + * @brief decompress fast normal light flux (F_FX) data + * + * @param cfg pointer to the compression configuration structure + * + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream + */ + +static int decompress_f_fx(const struct cmp_cfg *cfg) +{ + size_t i; + int stream_pos = 0; + uint32_t decoded_value; + struct decoder_setup setup_fx; + struct f_fx *data_buf = cfg->input_buf; + struct f_fx *model_buf = cfg->model_buf; + struct f_fx *up_model_buf = NULL; + struct f_fx *next_model_p; + struct f_fx model; + + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; + + stream_pos = decompress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); + + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } + + if (configure_decoder_setup(&setup_fx, cfg->cmp_par_fx, cfg->spill_fx, + cfg->round, max_used_bits.f_fx, cfg)) + return -1; + + for (i = 0; ; i++) { + stream_pos = decode_value(&decoded_value, model.fx, stream_pos, + &setup_fx); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].fx = decoded_value; + + if (up_model_buf) + up_model_buf[i].fx = cmp_up_model(data_buf[i].fx, model.fx, + cfg->model_value, setup_fx.lossy_par); + + if (i >= cfg->samples-1) + break; + + model = next_model_p[i]; + } + return stream_pos; +} + + +/** + * @brief decompress F_FX_DFX data + * + * @param cfg pointer to the compression configuration structure + * + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream + */ + +static int decompress_f_fx_efx(const struct cmp_cfg *cfg) +{ + size_t i; + int stream_pos = 0; + uint32_t decoded_value; + struct decoder_setup setup_fx, setup_efx; + struct f_fx_efx *data_buf = cfg->input_buf; + struct f_fx_efx *model_buf = cfg->model_buf; + struct f_fx_efx *up_model_buf = NULL; + struct f_fx_efx *next_model_p; + struct f_fx_efx model; + + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; + + stream_pos = decompress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); + + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } + + if (configure_decoder_setup(&setup_fx, cfg->cmp_par_fx, cfg->spill_fx, + cfg->round, max_used_bits.f_fx, cfg)) + return -1; + if (configure_decoder_setup(&setup_efx, cfg->cmp_par_efx, cfg->spill_efx, + cfg->round, max_used_bits.f_efx, cfg)) + return -1; + + for (i = 0; ; i++) { + stream_pos = decode_value(&decoded_value, model.fx, stream_pos, + &setup_fx); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].fx = decoded_value; + + stream_pos = decode_value(&decoded_value, model.efx, stream_pos, + &setup_efx); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].efx = decoded_value; + + if (up_model_buf) { + up_model_buf[i].fx = cmp_up_model(data_buf[i].fx, model.fx, + cfg->model_value, setup_fx.lossy_par); + up_model_buf[i].efx = cmp_up_model(data_buf[i].efx, model.efx, + cfg->model_value, setup_efx.lossy_par); + } + + if (i >= cfg->samples-1) + break; + + model = next_model_p[i]; + } + return stream_pos; +} + + +/** + * @brief decompress short F_FX_NCOB data + * + * @param cfg pointer to the compression configuration structure + * + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream + */ + +static int decompress_f_fx_ncob(const struct cmp_cfg *cfg) +{ + size_t i; + int stream_pos = 0; + uint32_t decoded_value; + struct decoder_setup setup_fx, setup_ncob; + struct f_fx_ncob *data_buf = cfg->input_buf; + struct f_fx_ncob *model_buf = cfg->model_buf; + struct f_fx_ncob *up_model_buf = NULL; + struct f_fx_ncob *next_model_p; + struct f_fx_ncob model; + + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; + + stream_pos = decompress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); + + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } + + if (configure_decoder_setup(&setup_fx, cfg->cmp_par_fx, cfg->spill_fx, + cfg->round, max_used_bits.f_fx, cfg)) + return -1; + if (configure_decoder_setup(&setup_ncob, cfg->cmp_par_ncob, cfg->spill_ncob, + cfg->round, max_used_bits.f_ncob, cfg)) + return -1; + + for (i = 0; ; i++) { + stream_pos = decode_value(&decoded_value, model.fx, stream_pos, + &setup_fx); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].fx = decoded_value; + + stream_pos = decode_value(&decoded_value, model.ncob_x, stream_pos, + &setup_ncob); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].ncob_x = decoded_value; + + stream_pos = decode_value(&decoded_value, model.ncob_y, stream_pos, + &setup_ncob); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].ncob_y = decoded_value; + + if (up_model_buf) { + up_model_buf[i].fx = cmp_up_model(data_buf[i].fx, model.fx, + cfg->model_value, setup_fx.lossy_par); + up_model_buf[i].ncob_x = cmp_up_model(data_buf[i].ncob_x, model.ncob_x, + cfg->model_value, setup_ncob.lossy_par); + up_model_buf[i].ncob_y = cmp_up_model(data_buf[i].ncob_y, model.ncob_y, + cfg->model_value, setup_ncob.lossy_par); + } + + if (i >= cfg->samples-1) + break; + + model = next_model_p[i]; + } + return stream_pos; +} + + +/** + * @brief decompress short F_FX_NCOB_ECOB data + * + * @param cfg pointer to the compression configuration structure + * + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream + */ + +static int decompress_f_fx_efx_ncob_ecob(const struct cmp_cfg *cfg) +{ + size_t i; + int stream_pos = 0; + uint32_t decoded_value; + struct decoder_setup setup_fx, setup_ncob, setup_efx, setup_ecob; + struct f_fx_efx_ncob_ecob *data_buf = cfg->input_buf; + struct f_fx_efx_ncob_ecob *model_buf = cfg->model_buf; + struct f_fx_efx_ncob_ecob *up_model_buf = NULL; + struct f_fx_efx_ncob_ecob *next_model_p; + struct f_fx_efx_ncob_ecob model; + + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; + + stream_pos = decompress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); + + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } + + if (configure_decoder_setup(&setup_fx, cfg->cmp_par_fx, cfg->spill_fx, + cfg->round, max_used_bits.f_fx, cfg)) + return -1; + if (configure_decoder_setup(&setup_ncob, cfg->cmp_par_ncob, cfg->spill_ncob, + cfg->round, max_used_bits.f_ncob, cfg)) + return -1; + if (configure_decoder_setup(&setup_efx, cfg->cmp_par_efx, cfg->spill_efx, + cfg->round, max_used_bits.f_efx, cfg)) + return -1; + if (configure_decoder_setup(&setup_ecob, cfg->cmp_par_ecob, cfg->spill_ecob, + cfg->round, max_used_bits.f_ecob, cfg)) + return -1; + + for (i = 0; ; i++) { + stream_pos = decode_value(&decoded_value, model.fx, stream_pos, + &setup_fx); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].fx = decoded_value; + + stream_pos = decode_value(&decoded_value, model.ncob_x, stream_pos, + &setup_ncob); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].ncob_x = decoded_value; + + stream_pos = decode_value(&decoded_value, model.ncob_y, stream_pos, + &setup_ncob); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].ncob_y = decoded_value; + + stream_pos = decode_value(&decoded_value, model.efx, stream_pos, + &setup_efx); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].efx = decoded_value; + + stream_pos = decode_value(&decoded_value, model.ecob_x, stream_pos, + &setup_ecob); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].ecob_x = decoded_value; + + stream_pos = decode_value(&decoded_value, model.ecob_y, stream_pos, + &setup_ecob); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].ecob_y = decoded_value; + + if (up_model_buf) { + up_model_buf[i].fx = cmp_up_model(data_buf[i].fx, model.fx, + cfg->model_value, setup_fx.lossy_par); + up_model_buf[i].ncob_x = cmp_up_model(data_buf[i].ncob_x, model.ncob_x, + cfg->model_value, setup_ncob.lossy_par); + up_model_buf[i].ncob_y = cmp_up_model(data_buf[i].ncob_y, model.ncob_y, + cfg->model_value, setup_ncob.lossy_par); + up_model_buf[i].efx = cmp_up_model(data_buf[i].efx, model.efx, + cfg->model_value, setup_efx.lossy_par); + up_model_buf[i].ecob_x = cmp_up_model(data_buf[i].ecob_x, model.ecob_x, + cfg->model_value, setup_ecob.lossy_par); + up_model_buf[i].ecob_y = cmp_up_model(data_buf[i].ecob_y, model.ecob_y, + cfg->model_value, setup_ecob.lossy_par); + } + + if (i >= cfg->samples-1) + break; + + model = next_model_p[i]; + } + return stream_pos; +} + + +/** + * @brief decompress long normal light flux (L_FX) data + * + * @param cfg pointer to the compression configuration structure + * + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream + */ + +static int decompress_l_fx(const struct cmp_cfg *cfg) +{ + size_t i; + int stream_pos = 0; + uint32_t decoded_value; + struct decoder_setup setup_exp_flags, setup_fx, setup_fx_var; + struct l_fx *data_buf = cfg->input_buf; + struct l_fx *model_buf = cfg->model_buf; + struct l_fx *up_model_buf = NULL; + struct l_fx *next_model_p; + struct l_fx model; + + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; + + stream_pos = decompress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); + + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } + + if (configure_decoder_setup(&setup_exp_flags, cfg->cmp_par_exp_flags, cfg->spill_exp_flags, + cfg->round, max_used_bits.l_exp_flags, cfg)) + return -1; + if (configure_decoder_setup(&setup_fx, cfg->cmp_par_fx, cfg->spill_fx, + cfg->round, max_used_bits.l_fx, cfg)) + return -1; + if (configure_decoder_setup(&setup_fx_var, cfg->cmp_par_fx_cob_variance, cfg->spill_fx_cob_variance, + cfg->round, max_used_bits.l_fx_variance, cfg)) + return -1; + + for (i = 0; ; i++) { + stream_pos = decode_value(&decoded_value, model.exp_flags, + stream_pos, &setup_exp_flags); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].exp_flags = decoded_value; + + stream_pos = decode_value(&decoded_value, model.fx, stream_pos, + &setup_fx); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].fx = decoded_value; + + stream_pos = decode_value(&decoded_value, model.fx_variance, stream_pos, + &setup_fx_var); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].fx_variance = decoded_value; + + if (up_model_buf) { + up_model_buf[i].exp_flags = cmp_up_model(data_buf[i].exp_flags, model.exp_flags, + cfg->model_value, setup_exp_flags.lossy_par); + up_model_buf[i].fx = cmp_up_model(data_buf[i].fx, model.fx, + cfg->model_value, setup_fx.lossy_par); + up_model_buf[i].fx_variance = cmp_up_model(data_buf[i].fx_variance, model.fx_variance, + cfg->model_value, setup_fx_var.lossy_par); + } + + if (i >= cfg->samples-1) + break; + + model = next_model_p[i]; + } + return stream_pos; +} + + +/** + * @brief decompress L_FX_EFX data + * + * @param cfg pointer to the compression configuration structure + * + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream + */ + +static int decompress_l_fx_efx(const struct cmp_cfg *cfg) +{ + size_t i; + int stream_pos = 0; + uint32_t decoded_value; + struct decoder_setup setup_exp_flags, setup_fx, setup_efx, setup_fx_var; + struct l_fx_efx *data_buf = cfg->input_buf; + struct l_fx_efx *model_buf = cfg->model_buf; + struct l_fx_efx *up_model_buf = NULL; + struct l_fx_efx *next_model_p; + struct l_fx_efx model; + + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; + + stream_pos = decompress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); + + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } + + if (configure_decoder_setup(&setup_exp_flags, cfg->cmp_par_exp_flags, cfg->spill_exp_flags, + cfg->round, max_used_bits.l_exp_flags, cfg)) + return -1; + if (configure_decoder_setup(&setup_fx, cfg->cmp_par_fx, cfg->spill_fx, + cfg->round, max_used_bits.l_fx, cfg)) + return -1; + if (configure_decoder_setup(&setup_efx, cfg->cmp_par_efx, cfg->spill_efx, + cfg->round, max_used_bits.l_efx, cfg)) + return -1; + if (configure_decoder_setup(&setup_fx_var, cfg->cmp_par_fx_cob_variance, cfg->spill_fx_cob_variance, + cfg->round, max_used_bits.l_fx_variance, cfg)) + return -1; + + for (i = 0; ; i++) { + stream_pos = decode_value(&decoded_value, model.exp_flags, + stream_pos, &setup_exp_flags); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].exp_flags = decoded_value; + + stream_pos = decode_value(&decoded_value, model.fx, stream_pos, + &setup_fx); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].fx = decoded_value; + + stream_pos = decode_value(&decoded_value, model.efx, stream_pos, + &setup_efx); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].efx = decoded_value; + + stream_pos = decode_value(&decoded_value, model.fx_variance, stream_pos, + &setup_fx_var); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].fx_variance = decoded_value; + + if (up_model_buf) { + up_model_buf[i].exp_flags = cmp_up_model(data_buf[i].exp_flags, model.exp_flags, + cfg->model_value, setup_exp_flags.lossy_par); + up_model_buf[i].fx = cmp_up_model(data_buf[i].fx, model.fx, + cfg->model_value, setup_fx.lossy_par); + up_model_buf[i].efx = cmp_up_model(data_buf[i].efx, model.efx, + cfg->model_value, setup_efx.lossy_par); + up_model_buf[i].fx_variance = cmp_up_model(data_buf[i].fx_variance, model.fx_variance, + cfg->model_value, setup_fx_var.lossy_par); + } + + if (i >= cfg->samples-1) + break; + + model = next_model_p[i]; + } + return stream_pos; +} + + +/** + * @brief decompress L_FX_NCOB data + * + * @param cfg pointer to the compression configuration structure + * + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream + */ + +static int decompress_l_fx_ncob(const struct cmp_cfg *cfg) +{ + size_t i; + int stream_pos = 0; + uint32_t decoded_value; + struct decoder_setup setup_exp_flags, setup_fx, setup_ncob, + setup_fx_var, setup_cob_var; + struct l_fx_ncob *data_buf = cfg->input_buf; + struct l_fx_ncob *model_buf = cfg->model_buf; + struct l_fx_ncob *up_model_buf = NULL; + struct l_fx_ncob *next_model_p; + struct l_fx_ncob model; + + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; + + stream_pos = decompress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); + + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } + + if (configure_decoder_setup(&setup_exp_flags, cfg->cmp_par_exp_flags, cfg->spill_exp_flags, + cfg->round, max_used_bits.l_exp_flags, cfg)) + return -1; + if (configure_decoder_setup(&setup_fx, cfg->cmp_par_fx, cfg->spill_fx, + cfg->round, max_used_bits.l_fx, cfg)) + return -1; + if (configure_decoder_setup(&setup_ncob, cfg->cmp_par_ncob, cfg->spill_ncob, + cfg->round, max_used_bits.l_ncob, cfg)) + return -1; + if (configure_decoder_setup(&setup_fx_var, cfg->cmp_par_fx_cob_variance, cfg->spill_fx_cob_variance, + cfg->round, max_used_bits.l_fx_variance, cfg)) + return -1; + if (configure_decoder_setup(&setup_cob_var, cfg->cmp_par_fx_cob_variance, cfg->spill_fx_cob_variance, + cfg->round, max_used_bits.l_cob_variance, cfg)) + return -1; + + for (i = 0; ; i++) { + stream_pos = decode_value(&decoded_value, model.exp_flags, + stream_pos, &setup_exp_flags); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].exp_flags = decoded_value; + + stream_pos = decode_value(&decoded_value, model.fx, stream_pos, + &setup_fx); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].fx = decoded_value; + + stream_pos = decode_value(&decoded_value, model.ncob_x, + stream_pos, &setup_ncob); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].ncob_x = decoded_value; + + stream_pos = decode_value(&decoded_value, model.ncob_y, + stream_pos, &setup_ncob); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].ncob_y = decoded_value; + + stream_pos = decode_value(&decoded_value, model.fx_variance, + stream_pos, &setup_fx_var); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].fx_variance = decoded_value; + + stream_pos = decode_value(&decoded_value, model.cob_x_variance, + stream_pos, &setup_cob_var); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].cob_x_variance = decoded_value; + + stream_pos = decode_value(&decoded_value, model.cob_y_variance, + stream_pos, &setup_cob_var); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].cob_y_variance = decoded_value; + + if (up_model_buf) { + up_model_buf[i].exp_flags = cmp_up_model(data_buf[i].exp_flags, model.exp_flags, + cfg->model_value, setup_exp_flags.lossy_par); + up_model_buf[i].fx = cmp_up_model(data_buf[i].fx, model.fx, + cfg->model_value, setup_fx.lossy_par); + up_model_buf[i].ncob_x = cmp_up_model(data_buf[i].ncob_x, model.ncob_x, + cfg->model_value, setup_ncob.lossy_par); + up_model_buf[i].ncob_y = cmp_up_model(data_buf[i].ncob_y, model.ncob_y, + cfg->model_value, setup_ncob.lossy_par); + up_model_buf[i].fx_variance = cmp_up_model(data_buf[i].fx_variance, model.fx_variance, + cfg->model_value, setup_fx_var.lossy_par); + up_model_buf[i].cob_x_variance = cmp_up_model(data_buf[i].cob_x_variance, model.cob_x_variance, + cfg->model_value, setup_cob_var.lossy_par); + up_model_buf[i].cob_y_variance = cmp_up_model(data_buf[i].cob_y_variance, model.cob_y_variance, + cfg->model_value, setup_cob_var.lossy_par); + } + + if (i >= cfg->samples-1) + break; + + model = next_model_p[i]; + } + return stream_pos; +} + + +/** + * @brief decompress L_FX_EFX_NCOB_ECOB data + * + * @param cfg pointer to the compression configuration structure + * + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream + */ + +static int decompress_l_fx_efx_ncob_ecob(const struct cmp_cfg *cfg) +{ + size_t i; + int stream_pos = 0; + uint32_t decoded_value; + struct decoder_setup setup_exp_flags, setup_fx, setup_ncob, setup_efx, + setup_ecob, setup_fx_var, setup_cob_var; + struct l_fx_efx_ncob_ecob *data_buf = cfg->input_buf; + struct l_fx_efx_ncob_ecob *model_buf = cfg->model_buf; + struct l_fx_efx_ncob_ecob *up_model_buf = NULL; + struct l_fx_efx_ncob_ecob *next_model_p; + struct l_fx_efx_ncob_ecob model; + + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; + + stream_pos = decompress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); + + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } + + if (configure_decoder_setup(&setup_exp_flags, cfg->cmp_par_exp_flags, cfg->spill_exp_flags, + cfg->round, max_used_bits.l_exp_flags, cfg)) + return -1; + if (configure_decoder_setup(&setup_fx, cfg->cmp_par_fx, cfg->spill_fx, + cfg->round, max_used_bits.l_fx, cfg)) + return -1; + if (configure_decoder_setup(&setup_ncob, cfg->cmp_par_ncob, cfg->spill_ncob, + cfg->round, max_used_bits.l_ncob, cfg)) + return -1; + if (configure_decoder_setup(&setup_efx, cfg->cmp_par_efx, cfg->spill_efx, + cfg->round, max_used_bits.l_efx, cfg)) + return -1; + if (configure_decoder_setup(&setup_ecob, cfg->cmp_par_ecob, cfg->spill_ecob, + cfg->round, max_used_bits.l_ecob, cfg)) + return -1; + if (configure_decoder_setup(&setup_fx_var, cfg->cmp_par_fx_cob_variance, cfg->spill_fx_cob_variance, + cfg->round, max_used_bits.l_fx_variance, cfg)) + return -1; + if (configure_decoder_setup(&setup_cob_var, cfg->cmp_par_fx_cob_variance, cfg->spill_fx_cob_variance, + cfg->round, max_used_bits.l_cob_variance, cfg)) + return -1; + + for (i = 0; ; i++) { + stream_pos = decode_value(&decoded_value, model.exp_flags, + stream_pos, &setup_exp_flags); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].exp_flags = decoded_value; + + stream_pos = decode_value(&decoded_value, model.fx, stream_pos, + &setup_fx); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].fx = decoded_value; + + stream_pos = decode_value(&decoded_value, model.ncob_x, + stream_pos, &setup_ncob); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].ncob_x = decoded_value; + + stream_pos = decode_value(&decoded_value, model.ncob_y, + stream_pos, &setup_ncob); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].ncob_y = decoded_value; + + stream_pos = decode_value(&decoded_value, model.efx, stream_pos, + &setup_efx); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].efx = decoded_value; + + stream_pos = decode_value(&decoded_value, model.ecob_x, + stream_pos, &setup_ecob); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].ecob_x = decoded_value; + + stream_pos = decode_value(&decoded_value, model.ecob_y, + stream_pos, &setup_ecob); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].ecob_y = decoded_value; + + stream_pos = decode_value(&decoded_value, model.fx_variance, + stream_pos, &setup_fx_var); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].fx_variance = decoded_value; + + stream_pos = decode_value(&decoded_value, model.cob_x_variance, + stream_pos, &setup_cob_var); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].cob_x_variance = decoded_value; + + stream_pos = decode_value(&decoded_value, model.cob_y_variance, + stream_pos, &setup_cob_var); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].cob_y_variance = decoded_value; + + if (up_model_buf) { + up_model_buf[i].exp_flags = cmp_up_model(data_buf[i].exp_flags, model.exp_flags, + cfg->model_value, setup_exp_flags.lossy_par); + up_model_buf[i].fx = cmp_up_model(data_buf[i].fx, model.fx, + cfg->model_value, setup_fx.lossy_par); + up_model_buf[i].ncob_x = cmp_up_model(data_buf[i].ncob_x, model.ncob_x, + cfg->model_value, setup_ncob.lossy_par); + up_model_buf[i].ncob_y = cmp_up_model(data_buf[i].ncob_y, model.ncob_y, + cfg->model_value, setup_ncob.lossy_par); + up_model_buf[i].fx_variance = cmp_up_model(data_buf[i].fx_variance, model.fx_variance, + cfg->model_value, setup_fx_var.lossy_par); + up_model_buf[i].cob_x_variance = cmp_up_model(data_buf[i].cob_x_variance, model.cob_x_variance, + cfg->model_value, setup_cob_var.lossy_par); + up_model_buf[i].cob_y_variance = cmp_up_model(data_buf[i].cob_y_variance, model.cob_y_variance, + cfg->model_value, setup_cob_var.lossy_par); + } + + if (i >= cfg->samples-1) + break; + + model = next_model_p[i]; + } + return stream_pos; +} + + +/** + * @brief decompress N-CAM offset data + * + * @param cfg pointer to the compression configuration structure + * + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream + */ + +static int decompress_nc_offset(const struct cmp_cfg *cfg) +{ + size_t i; + int stream_pos = 0; + uint32_t decoded_value; + struct decoder_setup setup_mean, setup_var; + struct nc_offset *data_buf = cfg->input_buf; + struct nc_offset *model_buf = cfg->model_buf; + struct nc_offset *up_model_buf = NULL; + struct nc_offset *next_model_p; + struct nc_offset model; + + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; + + stream_pos = decompress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); + + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } + + if (configure_decoder_setup(&setup_mean, cfg->cmp_par_mean, cfg->spill_mean, + cfg->round, max_used_bits.nc_offset_mean, cfg)) + return -1; + if (configure_decoder_setup(&setup_var, cfg->cmp_par_variance, cfg->spill_variance, + cfg->round, max_used_bits.nc_offset_variance, cfg)) + return -1; + + for (i = 0; ; i++) { + stream_pos = decode_value(&decoded_value, model.mean, stream_pos, + &setup_mean); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].mean = decoded_value; + + stream_pos = decode_value(&decoded_value, model.variance, stream_pos, + &setup_var); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].variance = decoded_value; + + if (up_model_buf) { + up_model_buf[i].mean = cmp_up_model(data_buf[i].mean, + model.mean, cfg->model_value, setup_mean.lossy_par); + up_model_buf[i].variance = cmp_up_model(data_buf[i].variance, + model.variance, cfg->model_value, setup_var.lossy_par); + } + + if (i >= cfg->samples-1) + break; + + model = next_model_p[i]; + } + return stream_pos; +} + + +/** + * @brief decompress N-CAM background data + * + * @param cfg pointer to the compression configuration structure + * + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream + */ + +static int decompress_nc_background(const struct cmp_cfg *cfg) +{ + size_t i; + int stream_pos = 0; + uint32_t decoded_value; + struct decoder_setup setup_mean, setup_var, setup_pix; + struct nc_background *data_buf = cfg->input_buf; + struct nc_background *model_buf = cfg->model_buf; + struct nc_background *up_model_buf = NULL; + struct nc_background *next_model_p; + struct nc_background model; + + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; + + stream_pos = decompress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); + + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } + + if (configure_decoder_setup(&setup_mean, cfg->cmp_par_mean, cfg->spill_mean, + cfg->round, max_used_bits.nc_background_mean, cfg)) + return -1; + if (configure_decoder_setup(&setup_var, cfg->cmp_par_variance, cfg->spill_variance, + cfg->round, max_used_bits.nc_background_variance, cfg)) + return -1; + if (configure_decoder_setup(&setup_pix, cfg->cmp_par_pixels_error, cfg->spill_pixels_error, + cfg->round, max_used_bits.nc_background_outlier_pixels, cfg)) + return -1; + + for (i = 0; ; i++) { + stream_pos = decode_value(&decoded_value, model.mean, stream_pos, + &setup_mean); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].mean = decoded_value; + + stream_pos = decode_value(&decoded_value, model.variance, stream_pos, + &setup_var); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].variance = decoded_value; + + stream_pos = decode_value(&decoded_value, model.outlier_pixels, stream_pos, + &setup_pix); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].outlier_pixels = decoded_value; + + if (up_model_buf) { + up_model_buf[i].mean = cmp_up_model(data_buf[i].mean, + model.mean, cfg->model_value, setup_mean.lossy_par); + up_model_buf[i].variance = cmp_up_model(data_buf[i].variance, + model.variance, cfg->model_value, setup_var.lossy_par); + up_model_buf[i].outlier_pixels = cmp_up_model(data_buf[i].outlier_pixels, + model.outlier_pixels, cfg->model_value, setup_pix.lossy_par); + } + + if (i >= cfg->samples-1) + break; + + model = next_model_p[i]; + } + return stream_pos; +} + + +/** + * @brief decompress N-CAM smearing data + * + * @param cfg pointer to the compression configuration structure + * + * @returns the bit length of the bitstream on success; negative on error, + * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * value in the bitstream + */ + +static int decompress_smearing(const struct cmp_cfg *cfg) +{ + size_t i; + int stream_pos = 0; + uint32_t decoded_value; + struct decoder_setup setup_mean, setup_var, setup_pix; + struct smearing *data_buf = cfg->input_buf; + struct smearing *model_buf = cfg->model_buf; + struct smearing *up_model_buf = NULL; + struct smearing *next_model_p; + struct smearing model; + + if (model_mode_is_used(cfg->cmp_mode)) + up_model_buf = cfg->icu_new_model_buf; + + stream_pos = decompress_multi_entry_hdr((void **)&data_buf, (void **)&model_buf, + (void **)&up_model_buf, cfg); + + if (model_mode_is_used(cfg->cmp_mode)) { + model = model_buf[0]; + next_model_p = &model_buf[1]; + } else { + memset(&model, 0, sizeof(model)); + next_model_p = data_buf; + } + + if (configure_decoder_setup(&setup_mean, cfg->cmp_par_mean, cfg->spill_mean, + cfg->round, max_used_bits.smeating_mean, cfg)) + return -1; + if (configure_decoder_setup(&setup_var, cfg->cmp_par_variance, cfg->spill_variance, + cfg->round, max_used_bits.smeating_variance_mean, cfg)) + return -1; + if (configure_decoder_setup(&setup_pix, cfg->cmp_par_pixels_error, cfg->spill_pixels_error, + cfg->round, max_used_bits.smearing_outlier_pixels, cfg)) + return -1; + + for (i = 0; ; i++) { + stream_pos = decode_value(&decoded_value, model.mean, stream_pos, + &setup_mean); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].mean = decoded_value; + + stream_pos = decode_value(&decoded_value, model.variance_mean, stream_pos, + &setup_var); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].variance_mean = decoded_value; + + stream_pos = decode_value(&decoded_value, model.outlier_pixels, stream_pos, + &setup_pix); + if (stream_pos <= 0) + return stream_pos; + data_buf[i].outlier_pixels = decoded_value; + + if (up_model_buf) { + up_model_buf[i].mean = cmp_up_model(data_buf[i].mean, + model.mean, cfg->model_value, setup_mean.lossy_par); + up_model_buf[i].variance_mean = cmp_up_model(data_buf[i].variance_mean, + model.variance_mean, cfg->model_value, setup_var.lossy_par); + up_model_buf[i].outlier_pixels = cmp_up_model(data_buf[i].outlier_pixels, + model.outlier_pixels, cfg->model_value, setup_pix.lossy_par); + } + + if (i >= cfg->samples-1) + break; + + model = next_model_p[i]; + } + return stream_pos; +} + + +static int decompressed_data_internal(struct cmp_cfg *cfg) +{ + int data_size, strem_len_bit = -1; + + if (!cfg) + return 0; /* or -1? */ + if (!cfg->icu_output_buf) + return -1; + + data_size = cmp_cal_size_of_data(cfg->samples, cfg->data_type); + if (!cfg->input_buf || !data_size) + return data_size; + + if (model_mode_is_used(cfg->cmp_mode)) + if (!cfg->model_buf) + return -1; + + if (cfg->cmp_mode == CMP_MODE_RAW) { + + if ((unsigned int)data_size < cfg->buffer_length/CHAR_BIT) + return -1; + + if (cfg->input_buf) { + memcpy(cfg->input_buf, cfg->icu_output_buf, data_size); + if (cmp_input_big_to_cpu_endianness(cfg->input_buf, data_size, cfg->data_type)) + return -1; + strem_len_bit = data_size * CHAR_BIT; + } + + } else { + switch (cfg->data_type) { + case DATA_TYPE_IMAGETTE: + case DATA_TYPE_IMAGETTE_ADAPTIVE: + case DATA_TYPE_SAT_IMAGETTE: + case DATA_TYPE_SAT_IMAGETTE_ADAPTIVE: + case DATA_TYPE_F_CAM_IMAGETTE: + case DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE: + strem_len_bit = decompress_imagette(cfg); + break; + case DATA_TYPE_S_FX: + strem_len_bit = decompress_s_fx(cfg); + break; + case DATA_TYPE_S_FX_DFX: + strem_len_bit = decompress_s_fx_efx(cfg); + break; + case DATA_TYPE_S_FX_NCOB: + strem_len_bit = decompress_s_fx_ncob(cfg); + break; + case DATA_TYPE_S_FX_DFX_NCOB_ECOB: + strem_len_bit = decompress_s_fx_efx_ncob_ecob(cfg); + break; + + case DATA_TYPE_F_FX: + strem_len_bit = decompress_f_fx(cfg); + break; + case DATA_TYPE_F_FX_DFX: + strem_len_bit = decompress_f_fx_efx(cfg); + break; + case DATA_TYPE_F_FX_NCOB: + strem_len_bit = decompress_f_fx_ncob(cfg); + break; + case DATA_TYPE_F_FX_DFX_NCOB_ECOB: + strem_len_bit = decompress_f_fx_efx_ncob_ecob(cfg); + break; + + case DATA_TYPE_L_FX: + strem_len_bit = decompress_l_fx(cfg); + break; + case DATA_TYPE_L_FX_DFX: + strem_len_bit = decompress_l_fx_efx(cfg); + break; + case DATA_TYPE_L_FX_NCOB: + strem_len_bit = decompress_l_fx_ncob(cfg); + break; + case DATA_TYPE_L_FX_DFX_NCOB_ECOB: + strem_len_bit = decompress_l_fx_efx_ncob_ecob(cfg); + break; + + case DATA_TYPE_OFFSET: + strem_len_bit = decompress_nc_offset(cfg); + break; + case DATA_TYPE_BACKGROUND: + strem_len_bit = decompress_nc_background(cfg); + break; + case DATA_TYPE_SMEARING: + strem_len_bit = decompress_smearing(cfg); + break; + + case DATA_TYPE_F_CAM_OFFSET: + case DATA_TYPE_F_CAM_BACKGROUND: + case DATA_TYPE_UNKNOWN: + default: + strem_len_bit = -1; + debug_print("Error: Compressed data type not supported.\n"); + break; + } + + } + /* TODO: is this usefull? if (strem_len_bit != data_size * CHAR_BIT) { */ + if (strem_len_bit <= 0) + return -1; + + return data_size; +} + + +int decompress_cmp_entiy(struct cmp_entity *ent, void *model_buf, + void *up_model_buf, void *decompressed_data) +{ + int err; + struct cmp_cfg cfg = {0}; + + cfg.model_buf = model_buf; + cfg.icu_new_model_buf = up_model_buf; + cfg.input_buf = decompressed_data; + + if (!ent) + return -1; + + err = cmp_ent_read_header(ent, &cfg); + if (err) + return -1; + + return decompressed_data_internal(&cfg); +} + + +/* model buffer is overwritten with updated model */ + +int decompress_data(uint32_t *compressed_data, void *de_model_buf, + const struct cmp_info *info, void *decompressed_data) +{ + int size_decomp_data; + struct cmp_cfg cfg = {0}; + + if (!compressed_data) + return -1; + + if (!info) + return -1; + + if (info->cmp_err) + return -1; + + if (model_mode_is_used(info->cmp_mode_used)) + if (!de_model_buf) + return -1; + /* TODO: add ohter modes */ + + if (!decompressed_data) + return -1; + + /* cfg.data_type = info->data_type_used; */ + cfg.cmp_mode = info->cmp_mode_used; + cfg.model_value = info->model_value_used; + cfg.round = info->round_used; + cfg.spill = info->spill_used; + cfg.golomb_par = info->golomb_par_used; + cfg.samples = info->samples_used; + cfg.icu_output_buf = compressed_data; + cfg.buffer_length = cmp_bit_to_4byte(info->cmp_size); + cfg.input_buf = decompressed_data; + cfg.model_buf = de_model_buf; + size_decomp_data = decompressed_data_internal(&cfg); + if (size_decomp_data <= 0) + return -1; + else + return 0; } diff --git a/test/cmp_icu/test_decmp.c b/test/cmp_icu/test_decmp.c index 523b64f..cb00bb0 100644 --- a/test/cmp_icu/test_decmp.c +++ b/test/cmp_icu/test_decmp.c @@ -10,12 +10,14 @@ #include "../../lib/decmp.c" /* .c file included to test static functions */ -/* returns the needed size of the compression entry header plus the max size of the +/** + * returns the needed size of the compression entry header plus the max size of the * compressed data if ent == NULL if ent is set the size of the compression - * entry */ + * entry (entity header + compressed data) + */ size_t icu_compress_data_entity(struct cmp_entity *ent, const struct cmp_cfg *cfg) { - size_t s, hdr_size; + size_t s; struct cmp_cfg cfg_cpy; int cmp_size_bits; @@ -25,33 +27,38 @@ size_t icu_compress_data_entity(struct cmp_entity *ent, const struct cmp_cfg *cf if (cfg->icu_output_buf) debug_print("Warning the set buffer for the compressed data is ignored! The compressed data are write to the compression entry."); - if (!ent) { - s = cmp_cal_size_of_data(cfg->buffer_length, cfg->data_type); - if (!s) - return 0; + s = cmp_cal_size_of_data(cfg->buffer_length, cfg->data_type); + if (!s) + return 0; + /* we round down to the next 4-byte allied address because we access the + * cmp_buffer in uint32_t words + */ + if (cfg->cmp_mode != CMP_MODE_RAW) + s &= ~0x3U; - hdr_size = cmp_ent_cal_hdr_size(cfg->data_type, cfg->cmp_mode == CMP_MODE_RAW); - if (!hdr_size) - return 0; + s = cmp_ent_create(ent, cfg->data_type, cfg->cmp_mode == CMP_MODE_RAW, s); - return s + hdr_size; - } + if (!ent || !s) + return s; cfg_cpy = *cfg; cfg_cpy.icu_output_buf = cmp_ent_get_data_buf(ent); - + if (!cfg_cpy.icu_output_buf) + return 0; cmp_size_bits = icu_compress_data(&cfg_cpy); if (cmp_size_bits < 0) return 0; + /* XXX overwrite the size of the compression entity with the size of the + * actual size of the compressed data */ + /* not all allocated memory is normally needed */ s = cmp_ent_create(ent, cfg->data_type, cfg->cmp_mode == CMP_MODE_RAW, - cmp_bit_to_4byte(cmp_size_bits)); - if (!s) - return 0; + cmp_bit_to_4byte(cmp_size_bits)); if (cmp_ent_write_cmp_pars(ent, cfg, cmp_size_bits)) return 0; + return s; } @@ -177,7 +184,7 @@ void test_decode_normal(void) TEST_ASSERT_EQUAL_HEX(sample, decoded_value); } - /* TODO error case: negativ stream_pos */ + /* TODO error case: negative stream_pos */ } @@ -288,12 +295,75 @@ void test_decompress_imagette_model(void) TEST_ASSERT_EQUAL_HEX(4, up_model[4]); } + + +#define CMP_PAR_UNUSED 0 /*TODO: remove this*/ +#define DATA_SAMPLES 5 +void test_cmp_decmp_s_fx_diff(void) +{ + size_t s; + int err; + + struct cmp_entity *ent; + const uint32_t MAX_VALUE = ~(~0U << MAX_USED_S_FX_BITS); + struct s_fx data_entry[DATA_SAMPLES] = { + {0,0}, {1,23}, {2,42}, {3,MAX_VALUE}, {3,MAX_VALUE>>1} }; + uint8_t data_to_compress[MULTI_ENTRY_HDR_SIZE + sizeof(data_entry)]; + struct s_fx *decompressed_data = NULL; + /* uint32_t *compressed_data = NULL; */ + uint32_t compressed_data_len_samples = DATA_SAMPLES; + struct cmp_cfg cfg; + + for (s = 0; s < MULTI_ENTRY_HDR_SIZE; s++) + data_to_compress[s] = s; + memcpy(&data_to_compress[MULTI_ENTRY_HDR_SIZE], data_entry, sizeof(data_entry)); + + cfg = cmp_cfg_icu_create(DATA_TYPE_S_FX, CMP_MODE_DIFF_MULTI, + CMP_PAR_UNUSED, CMP_LOSSLESS); + TEST_ASSERT(cfg.data_type != DATA_TYPE_UNKNOWN); + + s = cmp_cfg_icu_buffers(&cfg, data_to_compress, DATA_SAMPLES, NULL, NULL, + NULL, compressed_data_len_samples); + TEST_ASSERT_TRUE(s); + + err = cmp_cfg_fx_cob(&cfg, 2, 6, 4, 14, CMP_PAR_UNUSED, CMP_PAR_UNUSED, + CMP_PAR_UNUSED, CMP_PAR_UNUSED, CMP_PAR_UNUSED, + CMP_PAR_UNUSED, CMP_PAR_UNUSED, CMP_PAR_UNUSED); + TEST_ASSERT_FALSE(err); + + s = icu_compress_data_entity(NULL, &cfg); + TEST_ASSERT_TRUE(s); + ent = malloc(s); TEST_ASSERT_TRUE(ent); + s = icu_compress_data_entity(ent, &cfg); + TEST_ASSERT_TRUE(s); + + /* now decompress the data */ + s = decompress_cmp_entiy(ent, NULL, NULL, decompressed_data); + TEST_ASSERT_EQUAL_INT(cmp_cal_size_of_data(cfg.samples, cfg.data_type), s); + decompressed_data = malloc(s); TEST_ASSERT_TRUE(decompressed_data); + s = decompress_cmp_entiy(ent, NULL, NULL, decompressed_data); + TEST_ASSERT_EQUAL_INT(cmp_cal_size_of_data(cfg.samples, cfg.data_type), s); + + TEST_ASSERT_FALSE(memcmp(data_to_compress, decompressed_data, s)); + /* for (i = 0; i < samples; ++i) { */ + /* printf("%u == %u (round: %u)\n", data[i], decompressed_data[i], round); */ + /* uint32_t mask = ~0U << round; */ + /* if ((data[i]&mask) != (decompressed_data[i]&mask)) */ + /* TEST_ASSERT(0); */ + /* if (model_mode_is_used(cmp_mode)) */ + /* if (up_model[i] != de_up_model[i]) */ + /* TEST_ASSERT(0); */ + /* } */ +} +#undef DATA_SAMPLES + + int my_random(unsigned int min, unsigned int max) { - if (max-min > RAND_MAX) - TEST_ASSERT(0); if (min > max) TEST_ASSERT(0); + if (max-min > RAND_MAX) + TEST_ASSERT(0); return min + rand() / (RAND_MAX / (max - min + 1) + 1); } @@ -394,7 +464,7 @@ void test_imagette_random(void) /* create a compression configuration */ cfg = cmp_cfg_icu_create(data_type, cmp_mode, model_value, round); - TEST_ASSERT(cfg.data_type != DATA_TYPE_UNKOWN); + TEST_ASSERT(cfg.data_type != DATA_TYPE_UNKNOWN); cmp_data_size = cmp_cfg_icu_buffers(&cfg, data, samples, model, up_model, @@ -402,14 +472,14 @@ void test_imagette_random(void) TEST_ASSERT_TRUE(cmp_data_size); uint32_t golomb_par = my_random(MIN_RDCU_GOLOMB_PAR, MAX_RDCU_GOLOMB_PAR); - uint32_t max_spill = get_max_spill(golomb_par, data_type); + uint32_t max_spill = cmp_icu_max_spill(golomb_par); TEST_ASSERT(max_spill > 1); uint32_t spill = my_random(2, max_spill); error = cmp_cfg_icu_imagette(&cfg, golomb_par, spill); TEST_ASSERT_FALSE(error); - print_cfg(&cfg, 0); + /* print_cfg(&cfg, 0); */ s = icu_compress_data_entity(NULL, &cfg); TEST_ASSERT_TRUE(s); ent = malloc(s); TEST_ASSERT_TRUE(ent); @@ -424,7 +494,7 @@ void test_imagette_random(void) TEST_ASSERT_EQUAL_INT(samples * sizeof(*data), s); for (i = 0; i < samples; ++i) { - printf("%u == %u (round: %u)\n", data[i], decompressed_data[i], round); + /* printf("%u == %u (round: %u)\n", data[i], decompressed_data[i], round); */ uint32_t mask = ~0U << round; if ((data[i]&mask) != (decompressed_data[i]&mask)) TEST_ASSERT(0); @@ -440,3 +510,227 @@ void test_imagette_random(void) free(ent); free(decompressed_data); } + + +void test_s_fx_diff(void) +{ + size_t s, i; + uint8_t cmp_entity[88] = { + 0x80, 0x00, 0x00, 0x09, 0x00, 0x00, 0x58, 0x00, 0x00, 0x20, 0x04, 0xEE, 0x21, 0xBD, 0xB0, 0x1C, 0x04, 0xEE, 0x21, 0xBD, 0xB0, 0x41, 0x00, 0x08, 0x02, 0x08, 0xD0, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0A, 0x00, 0x01, 0x00, 0x00, 0x0A, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0xAE, 0xDE, 0x00, 0x00, 0x00, 0x73, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, + }; + + uint8_t result_data[32] = {0}; + struct multi_entry_hdr *hdr = (struct multi_entry_hdr *)result_data; + struct s_fx *data = (struct s_fx *)hdr->entry; + /* put some dummy data in the header*/ + for (i = 0; i < sizeof(*hdr); ++i) + result_data[i] = i; + data[0].exp_flags = 0; + data[0].fx = 0; + data[1].exp_flags = 1; + data[1].fx = 0xFFFFFF; + data[2].exp_flags = 3; + data[2].fx = 0x7FFFFF; + data[3].exp_flags = 0; + data[3].fx = 0; + + s = decompress_cmp_entiy((void *)cmp_entity, NULL, NULL, NULL); + TEST_ASSERT_EQUAL_INT(sizeof(result_data), s); + uint8_t *decompressed_data = malloc(s); + TEST_ASSERT_TRUE(decompressed_data); + s = decompress_cmp_entiy((void *)cmp_entity, NULL, NULL, decompressed_data); + TEST_ASSERT_EQUAL_INT(sizeof(result_data), s); + for (i = 0; i < s; ++i) { + TEST_ASSERT_EQUAL(result_data[i], decompressed_data[i]); + } +} + + +void test_s_fx_model(void) +{ + size_t s, i; + uint8_t compressed_data_buf[92] = { + 0x80, 0x00, 0x00, 0x09, 0x00, 0x00, 0x5C, 0x00, 0x00, 0x20, 0x04, 0xF0, 0xC2, 0xD3, 0x47, 0xE4, 0x04, 0xF0, 0xC2, 0xD3, 0x48, 0x16, 0x00, 0x08, 0x03, 0x08, 0xD0, 0x10, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0A, 0x00, 0x01, 0x00, 0x00, 0x0A, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x3B, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0x5B, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0x5D, 0x80, 0x00, 0x00, + }; + struct cmp_entity * cmp_entity = (struct cmp_entity *)compressed_data_buf; + + uint8_t model_buf[32]; + uint8_t decompressed_data[32]; + uint8_t up_model_buf[32]; + uint8_t exp_data_buf[32] = {0}; /* expected uncompressed data */ + uint8_t exp_up_model_buf[32] = {0}; + + struct multi_entry_hdr *model_collection = (struct multi_entry_hdr *)model_buf; + struct s_fx *model_data = (struct s_fx *)model_collection->entry; + + memset(model_collection, 0xFF, sizeof(*model_collection)); + model_data[0].exp_flags = 0; + model_data[0].fx = 0; + model_data[1].exp_flags = 3; + model_data[1].fx = 0x7FFFFF; + model_data[2].exp_flags = 0; + model_data[2].fx = 0xFFFFFF; + model_data[3].exp_flags = 3; + model_data[3].fx = 0xFFFFFF; + + struct multi_entry_hdr *exp_data_collection = (struct multi_entry_hdr *)exp_data_buf; + struct s_fx *exp_data = (struct s_fx *)exp_data_collection->entry; + /* put some dummy data in the header */ + for (i = 0; i < sizeof(*exp_data_collection); i++) + exp_data_buf[i] = i; + exp_data[0].exp_flags = 0; + exp_data[0].fx = 0; + exp_data[1].exp_flags = 1; + exp_data[1].fx = 0xFFFFFF; + exp_data[2].exp_flags = 3; + exp_data[2].fx = 0x7FFFFF; + exp_data[3].exp_flags = 0; + exp_data[3].fx = 0; + + struct multi_entry_hdr *exp_up_model_collection = (struct multi_entry_hdr *)exp_up_model_buf; + struct s_fx *exp_updated_model_data = (struct s_fx *)exp_up_model_collection->entry; + /* put some dummy data in the header*/ + for (i = 0; i < sizeof(*exp_up_model_collection); i++) + exp_up_model_buf[i] = i; + exp_updated_model_data[0].exp_flags = 0; + exp_updated_model_data[0].fx = 0; + exp_updated_model_data[1].exp_flags = 2; + exp_updated_model_data[1].fx = 0xBFFFFF; + exp_updated_model_data[2].exp_flags = 1; + exp_updated_model_data[2].fx = 0xBFFFFF; + exp_updated_model_data[3].exp_flags = 1; + exp_updated_model_data[3].fx = 0x7FFFFF; + + s = decompress_cmp_entiy(cmp_entity, model_buf, up_model_buf, decompressed_data); + TEST_ASSERT_EQUAL_INT(sizeof(exp_data_buf), s); + + TEST_ASSERT_FALSE(memcmp(exp_data_buf, decompressed_data, s)); + TEST_ASSERT_FALSE(memcmp(exp_up_model_buf, up_model_buf, s)); +} + +/* TODO: implement this! */ +void generate_random_test_data(void *data, int samples, + enum cmp_data_type data_type) +{ + int s = cmp_cal_size_of_data(samples, data_type); + memset(data, 0x0, s); +} + + +void test_random_compression_decompression(void) +{ + size_t s; + struct cmp_cfg cfg = {0}; + struct cmp_entity *cmp_ent; + void *decompressed_data; + void *decompressed_up_model = NULL; + + srand(0); /* TODO:XXX*/ + puts("--------------------------------------------------------------"); + + /* for (cfg.data_type = DATA_TYPE_IMAGETTE; TODO:!! implement this */ + /* cfg.data_type < DATA_TYPE_F_CAM_BACKGROUND+1; cfg.data_type++) { */ + for (cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.data_type < DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE+1; cfg.data_type++) { + cfg.samples = my_random(1,0x30000); + if (cfg.data_type == DATA_TYPE_OFFSET) + puts("FADF"); + + cfg.buffer_length = (CMP_ENTITY_MAX_SIZE - NON_IMAGETTE_HEADER_SIZE - MULTI_ENTRY_HDR_SIZE)/size_of_a_sample(cfg.data_type);; + s = cmp_cal_size_of_data(cfg.samples, cfg.data_type); + printf("%s\n", data_type2string(cfg.data_type)); + TEST_ASSERT_TRUE(s); + cfg.input_buf = calloc(1, s); + TEST_ASSERT_NOT_NULL(cfg.input_buf); + cfg.model_buf = calloc(1, s); + TEST_ASSERT_TRUE(cfg.model_buf); + decompressed_data = calloc(1, s); + TEST_ASSERT_NOT_NULL(decompressed_data); + cfg.icu_new_model_buf = calloc(1, s); + TEST_ASSERT_TRUE(cfg.icu_new_model_buf); + decompressed_up_model = calloc(1, s); + TEST_ASSERT_TRUE(decompressed_up_model); + cmp_ent = calloc(1, CMP_ENTITY_MAX_SIZE); + + generate_random_test_data(cfg.input_buf, cfg.samples, cfg.data_type); + generate_random_test_data(cfg.model_buf, cfg.samples, cfg.data_type); + + cfg.model_value = my_random(0,16); + /* cfg.round = my_random(0,3); /1* XXX *1/ */ + cfg.round = 0; + + cfg.golomb_par = my_random(MIN_RDCU_GOLOMB_PAR, MAX_RDCU_GOLOMB_PAR); + cfg.ap1_golomb_par = my_random(MIN_RDCU_GOLOMB_PAR, MAX_RDCU_GOLOMB_PAR); + cfg.ap2_golomb_par = my_random(MIN_RDCU_GOLOMB_PAR, MAX_RDCU_GOLOMB_PAR); + cfg.cmp_par_exp_flags = my_random(MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); + cfg.cmp_par_fx = my_random(MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); + cfg.cmp_par_ncob = my_random(MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); + cfg.cmp_par_efx = my_random(MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); + cfg.cmp_par_ecob = my_random(MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); + cfg.cmp_par_fx_cob_variance = my_random(MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); + cfg.cmp_par_mean = my_random(MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); + cfg.cmp_par_variance = my_random(MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); + cfg.cmp_par_pixels_error = my_random(MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); + + cfg.spill = my_random(MIN_RDCU_SPILL, cmp_icu_max_spill(cfg.golomb_par)); + cfg.ap1_spill = my_random(MIN_RDCU_SPILL, cmp_icu_max_spill(cfg.ap1_golomb_par)); + cfg.ap2_spill = my_random(MIN_RDCU_SPILL, cmp_icu_max_spill(cfg.ap2_golomb_par)); + if (!rdcu_supported_data_type_is_used(cfg.data_type)) { + cfg.spill_exp_flags = my_random(MIN_ICU_SPILL, cmp_icu_max_spill(cfg.cmp_par_exp_flags)); + cfg.spill_fx = my_random(MIN_ICU_SPILL, cmp_icu_max_spill(cfg.cmp_par_fx)); + cfg.spill_ncob = my_random(MIN_ICU_SPILL, cmp_icu_max_spill(cfg.cmp_par_ncob)); + cfg.spill_efx = my_random(MIN_ICU_SPILL, cmp_icu_max_spill(cfg.cmp_par_efx)); + cfg.spill_ecob = my_random(MIN_ICU_SPILL, cmp_icu_max_spill(cfg.cmp_par_ecob)); + cfg.spill_fx_cob_variance = my_random(MIN_ICU_SPILL, cmp_icu_max_spill(cfg.cmp_par_fx_cob_variance)); + cfg.spill_mean = my_random(MIN_ICU_SPILL, cmp_icu_max_spill(cfg.cmp_par_mean)); + cfg.spill_variance = my_random(MIN_ICU_SPILL, cmp_icu_max_spill(cfg.cmp_par_variance)); + cfg.spill_pixels_error = my_random(MIN_ICU_SPILL, cmp_icu_max_spill(cfg.cmp_par_pixels_error)); + } + + for (cfg.cmp_mode = CMP_MODE_RAW; cfg.cmp_mode < CMP_MODE_STUFF; cfg.cmp_mode++) { + int cmp_size, decompress_size; + + cmp_size = icu_compress_data_entity(cmp_ent, &cfg); + if (cmp_size <= 0) { + printf("cmp_size: %i\n", cmp_size); + print_cfg(&cfg, 0); + } + TEST_ASSERT_GREATER_THAN(0, cmp_size); + + /* now decompress the data */ + decompress_size = decompress_cmp_entiy(cmp_ent, cfg.model_buf, + decompressed_up_model, decompressed_data); + + TEST_ASSERT_EQUAL_INT(s, decompress_size); + if (memcmp(cfg.input_buf, decompressed_data, s)) { + print_cfg(&cfg, 0); + TEST_ASSERT_FALSE(memcmp(cfg.input_buf, decompressed_data, s)); + } + if (model_mode_is_used(cfg.cmp_mode)) + TEST_ASSERT_FALSE(memcmp(cfg.icu_new_model_buf, decompressed_up_model, s)); + + memset(cmp_ent, 0, CMP_ENTITY_MAX_SIZE); + memset(decompressed_data, 0, s); + memset(decompressed_up_model, 0, s); + memset(cfg.icu_new_model_buf, 0, s); + } + + free(cfg.model_buf); + cfg.model_buf = NULL; + free(cfg.input_buf); + cfg.input_buf = NULL; + free(cfg.icu_new_model_buf); + cfg.icu_new_model_buf = NULL; + free(cmp_ent); + cmp_ent = NULL; + free(decompressed_data); + decompressed_data = NULL; + free(decompressed_up_model); + decompressed_up_model = NULL; + + } +} -- GitLab From 00d1990f9c454949bd6541e7e4278ef23fe195f1 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Thu, 1 Sep 2022 14:08:34 +0200 Subject: [PATCH 28/46] add test for cmp data types --- test/cmp_data_types/meson.build | 12 +++++++++++ test/cmp_data_types/test_cmp_data_types.c | 26 +++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 test/cmp_data_types/meson.build create mode 100644 test/cmp_data_types/test_cmp_data_types.c diff --git a/test/cmp_data_types/meson.build b/test/cmp_data_types/meson.build new file mode 100644 index 0000000..8de2ef8 --- /dev/null +++ b/test/cmp_data_types/meson.build @@ -0,0 +1,12 @@ +test_case = files('test_cmp_data_types.c') +test_runner = test_runner_generator.process(test_case) + +test_cmp_icu = executable('test_cmp_data_types', + test_case, test_runner, + include_directories : incdir, + link_with : cmp_lib, + dependencies : unity_dep, + build_by_default : false +) + +test('Compression Data Types Unit Tests', test_cmp_icu) diff --git a/test/cmp_data_types/test_cmp_data_types.c b/test/cmp_data_types/test_cmp_data_types.c new file mode 100644 index 0000000..881f46a --- /dev/null +++ b/test/cmp_data_types/test_cmp_data_types.c @@ -0,0 +1,26 @@ +#include <stdint.h> + +#include "unity.h" +#include "cmp_data_types.h" + +/** + * @test cmp_cal_size_of_data + */ + +void test_cmp_cal_size_of_data(void) +{ + unsigned int s; + + s = cmp_cal_size_of_data(1, DATA_TYPE_IMAGETTE); + TEST_ASSERT_EQUAL_UINT(sizeof(uint16_t), s); + + s = cmp_cal_size_of_data(1, DATA_TYPE_F_FX); + TEST_ASSERT_EQUAL_UINT(sizeof(struct f_fx)+MULTI_ENTRY_HDR_SIZE, s); + + /* overflow tests */ + s = cmp_cal_size_of_data(0x1999999A, DATA_TYPE_BACKGROUND); + TEST_ASSERT_EQUAL_UINT(0, s); + s = cmp_cal_size_of_data(0x19999999, DATA_TYPE_BACKGROUND); + TEST_ASSERT_EQUAL_UINT(0, s); +} + -- GitLab From 326a32b1eacefed4ecc6b716c33524a427889a0b Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Fri, 2 Sep 2022 09:26:07 +0200 Subject: [PATCH 29/46] Change compression data types containing DFX to EFX --- include/cmp_support.h | 12 +++++----- lib/cmp_data_types.c | 24 +++++++++---------- lib/cmp_entity.c | 48 ++++++++++++++++++------------------- lib/cmp_icu.c | 12 +++++----- lib/cmp_io.c | 12 +++++----- lib/cmp_support.c | 24 +++++++++---------- lib/decmp.c | 16 ++++++------- test/cmp_icu/test_cmp_icu.c | 38 ++++++++++++++--------------- 8 files changed, 93 insertions(+), 93 deletions(-) diff --git a/include/cmp_support.h b/include/cmp_support.h index 5c85a99..aa455a5 100644 --- a/include/cmp_support.h +++ b/include/cmp_support.h @@ -103,17 +103,17 @@ enum cmp_data_type { DATA_TYPE_BACKGROUND, DATA_TYPE_SMEARING, DATA_TYPE_S_FX, - DATA_TYPE_S_FX_DFX, + DATA_TYPE_S_FX_EFX, DATA_TYPE_S_FX_NCOB, - DATA_TYPE_S_FX_DFX_NCOB_ECOB, + DATA_TYPE_S_FX_EFX_NCOB_ECOB, DATA_TYPE_L_FX, - DATA_TYPE_L_FX_DFX, + DATA_TYPE_L_FX_EFX, DATA_TYPE_L_FX_NCOB, - DATA_TYPE_L_FX_DFX_NCOB_ECOB, + DATA_TYPE_L_FX_EFX_NCOB_ECOB, DATA_TYPE_F_FX, - DATA_TYPE_F_FX_DFX, + DATA_TYPE_F_FX_EFX, DATA_TYPE_F_FX_NCOB, - DATA_TYPE_F_FX_DFX_NCOB_ECOB, + DATA_TYPE_F_FX_EFX_NCOB_ECOB, DATA_TYPE_F_CAM_IMAGETTE, DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE, DATA_TYPE_F_CAM_OFFSET, diff --git a/lib/cmp_data_types.c b/lib/cmp_data_types.c index 5a01f8b..6ce5f4b 100644 --- a/lib/cmp_data_types.c +++ b/lib/cmp_data_types.c @@ -135,37 +135,37 @@ size_t size_of_a_sample(enum cmp_data_type data_type) case DATA_TYPE_S_FX: sample_size = sizeof(struct s_fx); break; - case DATA_TYPE_S_FX_DFX: + case DATA_TYPE_S_FX_EFX: sample_size = sizeof(struct s_fx_efx); break; case DATA_TYPE_S_FX_NCOB: sample_size = sizeof(struct s_fx_ncob); break; - case DATA_TYPE_S_FX_DFX_NCOB_ECOB: + case DATA_TYPE_S_FX_EFX_NCOB_ECOB: sample_size = sizeof(struct s_fx_efx_ncob_ecob); break; case DATA_TYPE_L_FX: sample_size = sizeof(struct l_fx); break; - case DATA_TYPE_L_FX_DFX: + case DATA_TYPE_L_FX_EFX: sample_size = sizeof(struct l_fx_efx); break; case DATA_TYPE_L_FX_NCOB: sample_size = sizeof(struct l_fx_ncob); break; - case DATA_TYPE_L_FX_DFX_NCOB_ECOB: + case DATA_TYPE_L_FX_EFX_NCOB_ECOB: sample_size = sizeof(struct l_fx_efx_ncob_ecob); break; case DATA_TYPE_F_FX: sample_size = sizeof(struct f_fx); break; - case DATA_TYPE_F_FX_DFX: + case DATA_TYPE_F_FX_EFX: sample_size = sizeof(struct f_fx_efx); break; case DATA_TYPE_F_FX_NCOB: sample_size = sizeof(struct f_fx_ncob); break; - case DATA_TYPE_F_FX_DFX_NCOB_ECOB: + case DATA_TYPE_F_FX_EFX_NCOB_ECOB: sample_size = sizeof(struct f_fx_efx_ncob_ecob); break; case DATA_TYPE_F_CAM_OFFSET: @@ -493,37 +493,37 @@ int cmp_input_big_to_cpu_endianness(void *data, uint32_t data_size_byte, case DATA_TYPE_S_FX: be_to_cpus_s_fx(data, samples); break; - case DATA_TYPE_S_FX_DFX: + case DATA_TYPE_S_FX_EFX: be_to_cpus_s_fx_efx(data, samples); break; case DATA_TYPE_S_FX_NCOB: be_to_cpus_s_fx_ncob(data, samples); break; - case DATA_TYPE_S_FX_DFX_NCOB_ECOB: + case DATA_TYPE_S_FX_EFX_NCOB_ECOB: be_to_cpus_s_fx_efx_ncob_ecob(data, samples); break; case DATA_TYPE_L_FX: be_to_cpus_l_fx(data, samples); break; - case DATA_TYPE_L_FX_DFX: + case DATA_TYPE_L_FX_EFX: be_to_cpus_l_fx_efx(data, samples); break; case DATA_TYPE_L_FX_NCOB: be_to_cpus_l_fx_ncob(data, samples); break; - case DATA_TYPE_L_FX_DFX_NCOB_ECOB: + case DATA_TYPE_L_FX_EFX_NCOB_ECOB: be_to_cpus_l_fx_efx_ncob_ecob(data, samples); break; case DATA_TYPE_F_FX: be_to_cpus_f_fx(data, samples); break; - case DATA_TYPE_F_FX_DFX: + case DATA_TYPE_F_FX_EFX: be_to_cpus_f_fx_efx(data, samples); break; case DATA_TYPE_F_FX_NCOB: be_to_cpus_f_fx_ncob(data, samples); break; - case DATA_TYPE_F_FX_DFX_NCOB_ECOB: + case DATA_TYPE_F_FX_EFX_NCOB_ECOB: be_to_cpus_f_fx_efx_ncob_ecob(data, samples); break; /* TODO: implement F_CAM conversion */ diff --git a/lib/cmp_entity.c b/lib/cmp_entity.c index 6a5b040..c0b2898 100644 --- a/lib/cmp_entity.c +++ b/lib/cmp_entity.c @@ -82,17 +82,17 @@ uint32_t cmp_ent_cal_hdr_size(enum cmp_data_type data_type, int raw_mode_flag) case DATA_TYPE_BACKGROUND: case DATA_TYPE_SMEARING: case DATA_TYPE_S_FX: - case DATA_TYPE_S_FX_DFX: + case DATA_TYPE_S_FX_EFX: case DATA_TYPE_S_FX_NCOB: - case DATA_TYPE_S_FX_DFX_NCOB_ECOB: + case DATA_TYPE_S_FX_EFX_NCOB_ECOB: case DATA_TYPE_L_FX: - case DATA_TYPE_L_FX_DFX: + case DATA_TYPE_L_FX_EFX: case DATA_TYPE_L_FX_NCOB: - case DATA_TYPE_L_FX_DFX_NCOB_ECOB: + case DATA_TYPE_L_FX_EFX_NCOB_ECOB: case DATA_TYPE_F_FX: - case DATA_TYPE_F_FX_DFX: + case DATA_TYPE_F_FX_EFX: case DATA_TYPE_F_FX_NCOB: - case DATA_TYPE_F_FX_DFX_NCOB_ECOB: + case DATA_TYPE_F_FX_EFX_NCOB_ECOB: case DATA_TYPE_F_CAM_OFFSET: case DATA_TYPE_F_CAM_BACKGROUND: size = NON_IMAGETTE_HEADER_SIZE; @@ -1661,17 +1661,17 @@ void *cmp_ent_get_data_buf(struct cmp_entity *ent) case DATA_TYPE_BACKGROUND: case DATA_TYPE_SMEARING: case DATA_TYPE_S_FX: - case DATA_TYPE_S_FX_DFX: + case DATA_TYPE_S_FX_EFX: case DATA_TYPE_S_FX_NCOB: - case DATA_TYPE_S_FX_DFX_NCOB_ECOB: + case DATA_TYPE_S_FX_EFX_NCOB_ECOB: case DATA_TYPE_L_FX: - case DATA_TYPE_L_FX_DFX: + case DATA_TYPE_L_FX_EFX: case DATA_TYPE_L_FX_NCOB: - case DATA_TYPE_L_FX_DFX_NCOB_ECOB: + case DATA_TYPE_L_FX_EFX_NCOB_ECOB: case DATA_TYPE_F_FX: - case DATA_TYPE_F_FX_DFX: + case DATA_TYPE_F_FX_EFX: case DATA_TYPE_F_FX_NCOB: - case DATA_TYPE_F_FX_DFX_NCOB_ECOB: + case DATA_TYPE_F_FX_EFX_NCOB_ECOB: case DATA_TYPE_F_CAM_OFFSET: case DATA_TYPE_F_CAM_BACKGROUND: return ent->non_ima.cmp_data; @@ -1893,17 +1893,17 @@ int cmp_ent_write_cmp_pars(struct cmp_entity *ent, const struct cmp_cfg *cfg, return -1; break; case DATA_TYPE_S_FX: - case DATA_TYPE_S_FX_DFX: + case DATA_TYPE_S_FX_EFX: case DATA_TYPE_S_FX_NCOB: - case DATA_TYPE_S_FX_DFX_NCOB_ECOB: + case DATA_TYPE_S_FX_EFX_NCOB_ECOB: case DATA_TYPE_L_FX: - case DATA_TYPE_L_FX_DFX: + case DATA_TYPE_L_FX_EFX: case DATA_TYPE_L_FX_NCOB: - case DATA_TYPE_L_FX_DFX_NCOB_ECOB: + case DATA_TYPE_L_FX_EFX_NCOB_ECOB: case DATA_TYPE_F_FX: - case DATA_TYPE_F_FX_DFX: + case DATA_TYPE_F_FX_EFX: case DATA_TYPE_F_FX_NCOB: - case DATA_TYPE_F_FX_DFX_NCOB_ECOB: + case DATA_TYPE_F_FX_EFX_NCOB_ECOB: if (cmp_ent_set_non_ima_cmp_par1(ent, cfg->cmp_par_exp_flags)) return -1; if (cmp_ent_set_non_ima_spill1(ent, cfg->spill_exp_flags)) @@ -2206,17 +2206,17 @@ int cmp_ent_read_header(struct cmp_entity *ent, struct cmp_cfg *cfg) cfg->spill_pixels_error = cmp_ent_get_non_ima_spill3(ent); break; case DATA_TYPE_S_FX: - case DATA_TYPE_S_FX_DFX: + case DATA_TYPE_S_FX_EFX: case DATA_TYPE_S_FX_NCOB: - case DATA_TYPE_S_FX_DFX_NCOB_ECOB: + case DATA_TYPE_S_FX_EFX_NCOB_ECOB: case DATA_TYPE_L_FX: - case DATA_TYPE_L_FX_DFX: + case DATA_TYPE_L_FX_EFX: case DATA_TYPE_L_FX_NCOB: - case DATA_TYPE_L_FX_DFX_NCOB_ECOB: + case DATA_TYPE_L_FX_EFX_NCOB_ECOB: case DATA_TYPE_F_FX: - case DATA_TYPE_F_FX_DFX: + case DATA_TYPE_F_FX_EFX: case DATA_TYPE_F_FX_NCOB: - case DATA_TYPE_F_FX_DFX_NCOB_ECOB: + case DATA_TYPE_F_FX_EFX_NCOB_ECOB: cfg->cmp_par_exp_flags = cmp_ent_get_non_ima_cmp_par1(ent); cfg->spill_exp_flags = cmp_ent_get_non_ima_spill1(ent); cfg->cmp_par_fx = cmp_ent_get_non_ima_cmp_par2(ent); diff --git a/lib/cmp_icu.c b/lib/cmp_icu.c index c6d4a27..2d04120 100644 --- a/lib/cmp_icu.c +++ b/lib/cmp_icu.c @@ -2354,39 +2354,39 @@ int icu_compress_data(const struct cmp_cfg *cfg) case DATA_TYPE_S_FX: cmp_size = compress_s_fx(cfg); break; - case DATA_TYPE_S_FX_DFX: + case DATA_TYPE_S_FX_EFX: cmp_size = compress_s_fx_efx(cfg); break; case DATA_TYPE_S_FX_NCOB: cmp_size = compress_s_fx_ncob(cfg); break; - case DATA_TYPE_S_FX_DFX_NCOB_ECOB: + case DATA_TYPE_S_FX_EFX_NCOB_ECOB: cmp_size = compress_s_fx_efx_ncob_ecob(cfg); break; case DATA_TYPE_F_FX: cmp_size = compress_f_fx(cfg); break; - case DATA_TYPE_F_FX_DFX: + case DATA_TYPE_F_FX_EFX: cmp_size = compress_f_fx_efx(cfg); break; case DATA_TYPE_F_FX_NCOB: cmp_size = compress_f_fx_ncob(cfg); break; - case DATA_TYPE_F_FX_DFX_NCOB_ECOB: + case DATA_TYPE_F_FX_EFX_NCOB_ECOB: cmp_size = compress_f_fx_efx_ncob_ecob(cfg); break; case DATA_TYPE_L_FX: cmp_size = compress_l_fx(cfg); break; - case DATA_TYPE_L_FX_DFX: + case DATA_TYPE_L_FX_EFX: cmp_size = compress_l_fx_efx(cfg); break; case DATA_TYPE_L_FX_NCOB: cmp_size = compress_l_fx_ncob(cfg); break; - case DATA_TYPE_L_FX_DFX_NCOB_ECOB: + case DATA_TYPE_L_FX_EFX_NCOB_ECOB: cmp_size = compress_l_fx_efx_ncob_ecob(cfg); break; diff --git a/lib/cmp_io.c b/lib/cmp_io.c index 83c6906..be0f8ed 100644 --- a/lib/cmp_io.c +++ b/lib/cmp_io.c @@ -46,17 +46,17 @@ static const struct { {DATA_TYPE_BACKGROUND, "DATA_TYPE_BACKGROUND"}, {DATA_TYPE_SMEARING, "DATA_TYPE_SMEARING"}, {DATA_TYPE_S_FX, "DATA_TYPE_S_FX"}, - {DATA_TYPE_S_FX_DFX, "DATA_TYPE_S_FX_DFX"}, + {DATA_TYPE_S_FX_EFX, "DATA_TYPE_S_FX_EFX"}, {DATA_TYPE_S_FX_NCOB, "DATA_TYPE_S_FX_NCOB"}, - {DATA_TYPE_S_FX_DFX_NCOB_ECOB, "DATA_TYPE_S_FX_DFX_NCOB_ECOB"}, + {DATA_TYPE_S_FX_EFX_NCOB_ECOB, "DATA_TYPE_S_FX_EFX_NCOB_ECOB"}, {DATA_TYPE_L_FX, "DATA_TYPE_L_FX"}, - {DATA_TYPE_L_FX_DFX, "DATA_TYPE_L_FX_DFX"}, + {DATA_TYPE_L_FX_EFX, "DATA_TYPE_L_FX_EFX"}, {DATA_TYPE_L_FX_NCOB, "DATA_TYPE_L_FX_NCOB"}, - {DATA_TYPE_L_FX_DFX_NCOB_ECOB, "DATA_TYPE_L_FX_DFX_NCOB_ECOB"}, + {DATA_TYPE_L_FX_EFX_NCOB_ECOB, "DATA_TYPE_L_FX_EFX_NCOB_ECOB"}, {DATA_TYPE_F_FX, "DATA_TYPE_F_FX"}, - {DATA_TYPE_F_FX_DFX, "DATA_TYPE_F_FX_DFX"}, + {DATA_TYPE_F_FX_EFX, "DATA_TYPE_F_FX_EFX"}, {DATA_TYPE_F_FX_NCOB, "DATA_TYPE_F_FX_NCOB"}, - {DATA_TYPE_F_FX_DFX_NCOB_ECOB, "DATA_TYPE_F_FX_DFX_NCOB_ECOB"}, + {DATA_TYPE_F_FX_EFX_NCOB_ECOB, "DATA_TYPE_F_FX_EFX_NCOB_ECOB"}, {DATA_TYPE_F_CAM_IMAGETTE, "DATA_TYPE_F_CAM_IMAGETTE"}, {DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE, "DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE"}, {DATA_TYPE_F_CAM_OFFSET, "DATA_TYPE_F_CAM_OFFSET"}, diff --git a/lib/cmp_support.c b/lib/cmp_support.c index cfa7dc5..ae7941b 100644 --- a/lib/cmp_support.c +++ b/lib/cmp_support.c @@ -297,17 +297,17 @@ int cmp_fx_cob_data_type_is_used(enum cmp_data_type data_type) { switch (data_type) { case DATA_TYPE_S_FX: - case DATA_TYPE_S_FX_DFX: + case DATA_TYPE_S_FX_EFX: case DATA_TYPE_S_FX_NCOB: - case DATA_TYPE_S_FX_DFX_NCOB_ECOB: + case DATA_TYPE_S_FX_EFX_NCOB_ECOB: case DATA_TYPE_L_FX: - case DATA_TYPE_L_FX_DFX: + case DATA_TYPE_L_FX_EFX: case DATA_TYPE_L_FX_NCOB: - case DATA_TYPE_L_FX_DFX_NCOB_ECOB: + case DATA_TYPE_L_FX_EFX_NCOB_ECOB: case DATA_TYPE_F_FX: - case DATA_TYPE_F_FX_DFX: + case DATA_TYPE_F_FX_EFX: case DATA_TYPE_F_FX_NCOB: - case DATA_TYPE_F_FX_DFX_NCOB_ECOB: + case DATA_TYPE_F_FX_EFX_NCOB_ECOB: return 1; default: return 0; @@ -723,7 +723,7 @@ int cmp_cfg_fx_cob_is_valid(const struct cmp_cfg *cfg) case DATA_TYPE_S_FX: check_exp_flags = 1; break; - case DATA_TYPE_S_FX_DFX: + case DATA_TYPE_S_FX_EFX: check_exp_flags = 1; check_efx = 1; break; @@ -731,7 +731,7 @@ int cmp_cfg_fx_cob_is_valid(const struct cmp_cfg *cfg) check_exp_flags = 1; check_ncob = 1; break; - case DATA_TYPE_S_FX_DFX_NCOB_ECOB: + case DATA_TYPE_S_FX_EFX_NCOB_ECOB: check_exp_flags = 1; check_ncob = 1; check_efx = 1; @@ -741,7 +741,7 @@ int cmp_cfg_fx_cob_is_valid(const struct cmp_cfg *cfg) check_exp_flags = 1; check_var = 1; break; - case DATA_TYPE_L_FX_DFX: + case DATA_TYPE_L_FX_EFX: check_exp_flags = 1; check_efx = 1; check_var = 1; @@ -751,7 +751,7 @@ int cmp_cfg_fx_cob_is_valid(const struct cmp_cfg *cfg) check_ncob = 1; check_var = 1; break; - case DATA_TYPE_L_FX_DFX_NCOB_ECOB: + case DATA_TYPE_L_FX_EFX_NCOB_ECOB: check_exp_flags = 1; check_ncob = 1; check_efx = 1; @@ -760,13 +760,13 @@ int cmp_cfg_fx_cob_is_valid(const struct cmp_cfg *cfg) break; case DATA_TYPE_F_FX: break; - case DATA_TYPE_F_FX_DFX: + case DATA_TYPE_F_FX_EFX: check_efx = 1; break; case DATA_TYPE_F_FX_NCOB: check_ncob = 1; break; - case DATA_TYPE_F_FX_DFX_NCOB_ECOB: + case DATA_TYPE_F_FX_EFX_NCOB_ECOB: check_ncob = 1; check_efx = 1; check_ecob = 1; diff --git a/lib/decmp.c b/lib/decmp.c index 3c7acfb..534fc46 100644 --- a/lib/decmp.c +++ b/lib/decmp.c @@ -664,7 +664,7 @@ static int decompress_s_fx(const struct cmp_cfg *cfg) /** - * @brief decompress S_FX_DFX data + * @brief decompress S_FX_EFX data * * @param cfg pointer to the compression configuration structure * @@ -1019,7 +1019,7 @@ static int decompress_f_fx(const struct cmp_cfg *cfg) /** - * @brief decompress F_FX_DFX data + * @brief decompress F_FX_EFX data * * @param cfg pointer to the compression configuration structure * @@ -2005,39 +2005,39 @@ static int decompressed_data_internal(struct cmp_cfg *cfg) case DATA_TYPE_S_FX: strem_len_bit = decompress_s_fx(cfg); break; - case DATA_TYPE_S_FX_DFX: + case DATA_TYPE_S_FX_EFX: strem_len_bit = decompress_s_fx_efx(cfg); break; case DATA_TYPE_S_FX_NCOB: strem_len_bit = decompress_s_fx_ncob(cfg); break; - case DATA_TYPE_S_FX_DFX_NCOB_ECOB: + case DATA_TYPE_S_FX_EFX_NCOB_ECOB: strem_len_bit = decompress_s_fx_efx_ncob_ecob(cfg); break; case DATA_TYPE_F_FX: strem_len_bit = decompress_f_fx(cfg); break; - case DATA_TYPE_F_FX_DFX: + case DATA_TYPE_F_FX_EFX: strem_len_bit = decompress_f_fx_efx(cfg); break; case DATA_TYPE_F_FX_NCOB: strem_len_bit = decompress_f_fx_ncob(cfg); break; - case DATA_TYPE_F_FX_DFX_NCOB_ECOB: + case DATA_TYPE_F_FX_EFX_NCOB_ECOB: strem_len_bit = decompress_f_fx_efx_ncob_ecob(cfg); break; case DATA_TYPE_L_FX: strem_len_bit = decompress_l_fx(cfg); break; - case DATA_TYPE_L_FX_DFX: + case DATA_TYPE_L_FX_EFX: strem_len_bit = decompress_l_fx_efx(cfg); break; case DATA_TYPE_L_FX_NCOB: strem_len_bit = decompress_l_fx_ncob(cfg); break; - case DATA_TYPE_L_FX_DFX_NCOB_ECOB: + case DATA_TYPE_L_FX_EFX_NCOB_ECOB: strem_len_bit = decompress_l_fx_efx_ncob_ecob(cfg); break; diff --git a/test/cmp_icu/test_cmp_icu.c b/test/cmp_icu/test_cmp_icu.c index d2c46ce..9038a39 100644 --- a/test/cmp_icu/test_cmp_icu.c +++ b/test/cmp_icu/test_cmp_icu.c @@ -582,17 +582,17 @@ void test_cmp_cfg_fx_cob(void) cmp_par_efx, spillover_efx, cmp_par_ecob, spillover_ecob, cmp_par_fx_cob_variance, spillover_fx_cob_variance); if (data_type == DATA_TYPE_S_FX || - data_type == DATA_TYPE_S_FX_DFX || + data_type == DATA_TYPE_S_FX_EFX || data_type == DATA_TYPE_S_FX_NCOB || - data_type == DATA_TYPE_S_FX_DFX_NCOB_ECOB || + data_type == DATA_TYPE_S_FX_EFX_NCOB_ECOB || data_type == DATA_TYPE_L_FX || - data_type == DATA_TYPE_L_FX_DFX || + data_type == DATA_TYPE_L_FX_EFX || data_type == DATA_TYPE_L_FX_NCOB || - data_type == DATA_TYPE_L_FX_DFX_NCOB_ECOB || + data_type == DATA_TYPE_L_FX_EFX_NCOB_ECOB || data_type == DATA_TYPE_F_FX || - data_type == DATA_TYPE_F_FX_DFX || + data_type == DATA_TYPE_F_FX_EFX || data_type == DATA_TYPE_F_FX_NCOB || - data_type == DATA_TYPE_F_FX_DFX_NCOB_ECOB) { + data_type == DATA_TYPE_F_FX_EFX_NCOB_ECOB) { TEST_ASSERT_FALSE(error); TEST_ASSERT_EQUAL_INT(data_type, cfg.data_type); TEST_ASSERT_EQUAL_INT(2, cfg.cmp_par_fx); @@ -665,8 +665,8 @@ void test_cmp_cfg_fx_cob(void) TEST_ASSERT_TRUE(error); - /* test DATA_TYPE_S_FX_DFX */ - cfg = cmp_cfg_icu_create(DATA_TYPE_S_FX_DFX, CMP_MODE_MODEL_ZERO, 0, 1); + /* test DATA_TYPE_S_FX_EFX */ + cfg = cmp_cfg_icu_create(DATA_TYPE_S_FX_EFX, CMP_MODE_MODEL_ZERO, 0, 1); cmp_par_exp_flags = MAX_ICU_GOLOMB_PAR; spillover_exp_flags = cmp_icu_max_spill(cmp_par_exp_flags); cmp_par_fx = MIN_ICU_GOLOMB_PAR; @@ -735,8 +735,8 @@ void test_cmp_cfg_fx_cob(void) TEST_ASSERT_TRUE(error); - /* test DATA_TYPE_S_FX_DFX_NCOB_ECOB */ - cfg = cmp_cfg_icu_create(DATA_TYPE_S_FX_DFX_NCOB_ECOB, CMP_MODE_DIFF_ZERO, 7, CMP_LOSSLESS); + /* test DATA_TYPE_S_FX_EFX_NCOB_ECOB */ + cfg = cmp_cfg_icu_create(DATA_TYPE_S_FX_EFX_NCOB_ECOB, CMP_MODE_DIFF_ZERO, 7, CMP_LOSSLESS); cmp_par_exp_flags = MAX_ICU_GOLOMB_PAR; spillover_exp_flags = cmp_icu_max_spill(cmp_par_exp_flags); cmp_par_fx = MIN_ICU_GOLOMB_PAR; @@ -810,8 +810,8 @@ void test_cmp_cfg_fx_cob(void) TEST_ASSERT_TRUE(error); - /* DATA_TYPE_L_FX_DFX */ - cfg = cmp_cfg_icu_create(DATA_TYPE_L_FX_DFX, CMP_MODE_DIFF_ZERO, 7, CMP_LOSSLESS); + /* DATA_TYPE_L_FX_EFX */ + cfg = cmp_cfg_icu_create(DATA_TYPE_L_FX_EFX, CMP_MODE_DIFF_ZERO, 7, CMP_LOSSLESS); cmp_par_exp_flags = MAX_ICU_GOLOMB_PAR; spillover_exp_flags = cmp_icu_max_spill(cmp_par_exp_flags); cmp_par_fx = MIN_ICU_GOLOMB_PAR; @@ -870,8 +870,8 @@ void test_cmp_cfg_fx_cob(void) TEST_ASSERT_EQUAL_INT(spillover_fx_cob_variance, cfg.spill_fx_cob_variance); - /* DATA_TYPE_L_FX_DFX_NCOB_ECOB */ - cfg = cmp_cfg_icu_create(DATA_TYPE_L_FX_DFX_NCOB_ECOB, CMP_MODE_DIFF_ZERO, 7, CMP_LOSSLESS); + /* DATA_TYPE_L_FX_EFX_NCOB_ECOB */ + cfg = cmp_cfg_icu_create(DATA_TYPE_L_FX_EFX_NCOB_ECOB, CMP_MODE_DIFF_ZERO, 7, CMP_LOSSLESS); cmp_par_exp_flags = MAX_ICU_GOLOMB_PAR; spillover_exp_flags = cmp_icu_max_spill(cmp_par_exp_flags); cmp_par_fx = MIN_ICU_GOLOMB_PAR; @@ -928,8 +928,8 @@ void test_cmp_cfg_fx_cob(void) TEST_ASSERT_EQUAL_INT(spillover_fx, cfg.spill_fx); - /* DATA_TYPE_F_FX_DFX */ - cfg = cmp_cfg_icu_create(DATA_TYPE_F_FX_DFX, CMP_MODE_DIFF_ZERO, 7, CMP_LOSSLESS); + /* DATA_TYPE_F_FX_EFX */ + cfg = cmp_cfg_icu_create(DATA_TYPE_F_FX_EFX, CMP_MODE_DIFF_ZERO, 7, CMP_LOSSLESS); cmp_par_exp_flags = ~0; /* invalid parameter */ spillover_exp_flags = ~0; /* invalid parameter */ cmp_par_fx = MIN_ICU_GOLOMB_PAR; @@ -980,8 +980,8 @@ void test_cmp_cfg_fx_cob(void) TEST_ASSERT_EQUAL_INT(spillover_ncob, cfg.spill_ncob); - /* DATA_TYPE_F_FX_DFX_NCOB_ECOB */ - cfg = cmp_cfg_icu_create(DATA_TYPE_F_FX_DFX_NCOB_ECOB, CMP_MODE_DIFF_ZERO, 7, CMP_LOSSLESS); + /* DATA_TYPE_F_FX_EFX_NCOB_ECOB */ + cfg = cmp_cfg_icu_create(DATA_TYPE_F_FX_EFX_NCOB_ECOB, CMP_MODE_DIFF_ZERO, 7, CMP_LOSSLESS); cmp_par_exp_flags = ~0; /* invalid parameter */ spillover_exp_flags = ~0; /* invalid parameter */ cmp_par_fx = MIN_ICU_GOLOMB_PAR; @@ -2920,7 +2920,7 @@ void test_compress_s_fx_efx_model_multi(void) cmp_set_max_used_bits(&max_used_bits); /* setup configuration */ - cfg.data_type = DATA_TYPE_S_FX_DFX; + cfg.data_type = DATA_TYPE_S_FX_EFX; cfg.cmp_mode = CMP_MODE_MODEL_MULTI; cfg.model_value = 16; cfg.samples = 6; -- GitLab From 89aeee27665395540068c5aa9be97822caf37bfd Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Thu, 8 Sep 2022 16:43:03 +0200 Subject: [PATCH 30/46] fix some typos --- cmp_tool.c | 2 +- include/cmp_data_types.h | 4 +- include/cmp_support.h | 2 +- lib/cmp_data_types.c | 4 +- lib/cmp_icu.c | 60 +- lib/cmp_support.c | 6 +- lib/decmp.c | 46 +- test/cmp_icu/test_cmp_icu.c | 1391 ++++++++++++++++++++++++++++++++++- 8 files changed, 1428 insertions(+), 87 deletions(-) diff --git a/cmp_tool.c b/cmp_tool.c index 0cf72ef..86302b3 100644 --- a/cmp_tool.c +++ b/cmp_tool.c @@ -655,7 +655,7 @@ static int cmp_gernate_rdcu_info(const struct cmp_cfg *cfg, int cmp_size_bit, st info->rdcu_new_model_adr_used = cfg->rdcu_new_model_adr; info->rdcu_cmp_adr_used = cfg->rdcu_buffer_adr; - if (cmp_size_bit == CMP_ERROR_SAMLL_BUF) + if (cmp_size_bit == CMP_ERROR_SMALL_BUF) /* the icu_output_buf is to small to store the whole bitstream */ info->cmp_err |= 1UL << SMALL_BUFFER_ERR_BIT; /* set small buffer error */ if (cmp_size_bit < 0) diff --git a/include/cmp_data_types.h b/include/cmp_data_types.h index 8878557..41a39f7 100644 --- a/include/cmp_data_types.h +++ b/include/cmp_data_types.h @@ -108,8 +108,8 @@ struct cmp_max_used_bits { unsigned int nc_background_mean; unsigned int nc_background_variance; unsigned int nc_background_outlier_pixels; - unsigned int smeating_mean; - unsigned int smeating_variance_mean; + unsigned int smearing_mean; + unsigned int smearing_variance_mean; unsigned int smearing_outlier_pixels; unsigned int fc_imagette; unsigned int fc_offset_mean; diff --git a/include/cmp_support.h b/include/cmp_support.h index aa455a5..b69eb5c 100644 --- a/include/cmp_support.h +++ b/include/cmp_support.h @@ -24,7 +24,7 @@ /* return code if the bitstream buffer is too small to store the whole bitstream */ -#define CMP_ERROR_SAMLL_BUF -2 +#define CMP_ERROR_SMALL_BUF -2 /* return code if the value or the model is bigger than the max_used_bits * parameter allows diff --git a/lib/cmp_data_types.c b/lib/cmp_data_types.c index 6ce5f4b..3064e6c 100644 --- a/lib/cmp_data_types.c +++ b/lib/cmp_data_types.c @@ -50,8 +50,8 @@ struct cmp_max_used_bits max_used_bits = { MAX_USED_NC_BACKGROUND_MEAN_BITS, /* nc_background_mean */ MAX_USED_NC_BACKGROUND_VARIANCE_BITS, /* nc_background_variance */ MAX_USED_NC_BACKGROUND_OUTLIER_PIXELS_BITS, /* nc_background_outlier_pixels */ - MAX_USED_SMEARING_MEAN_BITS, /* smeating_mean */ - MAX_USED_SMEARING_VARIANCE_MEAN_BITS, /* smeating_variance_mean */ + MAX_USED_SMEARING_MEAN_BITS, /* smearing_mean */ + MAX_USED_SMEARING_VARIANCE_MEAN_BITS, /* smearing_variance_mean */ MAX_USED_SMEARING_OUTLIER_PIXELS_BITS, /* smearing_outlier_pixels */ MAX_USED_FC_IMAGETTE_BITS, /* fc_imagette */ MAX_USED_FC_OFFSET_MEAN_BITS, /* fc_offset_mean */ diff --git a/lib/cmp_icu.c b/lib/cmp_icu.c index 2d04120..107b85d 100644 --- a/lib/cmp_icu.c +++ b/lib/cmp_icu.c @@ -306,7 +306,7 @@ static uint32_t map_to_pos(uint32_t value_to_map, unsigned int max_data_bits) * ignored if bitstream_adr is NULL * * @returns length in bits of the generated bitstream on success; returns - * negative in case of erroneous input; returns CMP_ERROR_SAMLL_BUF if + * negative in case of erroneous input; returns CMP_ERROR_SMALL_BUF if * the bitstream buffer is too small to put the value in the bitstream */ @@ -335,7 +335,7 @@ static int put_n_bits32(uint32_t value, unsigned int n_bits, int bit_offset, /* Check if bitstream buffer is large enough */ if ((unsigned int)stream_len > max_stream_len) { debug_print("Error: The buffer for the compressed data is too small to hold the compressed data. Try a larger buffer_length parameter.\n"); - return CMP_ERROR_SAMLL_BUF; + return CMP_ERROR_SMALL_BUF; } /* (M) is the n_bits parameter large enough to cover all value bits; the @@ -510,7 +510,7 @@ static uint32_t golomb_encoder(uint32_t value, uint32_t m, uint32_t log2_m, * @param setup pointer to the encoder setup * * @returns the bit length of the bitstream with the added encoded value on - * success; negative on error, CMP_ERROR_SAMLL_BUF if the bitstream buffer + * success; negative on error, CMP_ERROR_SMALL_BUF if the bitstream buffer * is too small to put the value in the bitstream */ @@ -537,7 +537,7 @@ static int encode_normal(uint32_t value, int stream_len, * @param setup pointer to the encoder setup * * @returns the bit length of the bitstream with the added encoded value on - * success; negative on error, CMP_ERROR_SAMLL_BUF if the bitstream buffer + * success; negative on error, CMP_ERROR_SMALL_BUF if the bitstream buffer * is too small to put the value in the bitstream * * @note no check if the data or model are in the allowed range @@ -587,7 +587,7 @@ static int encode_value_zero(uint32_t data, uint32_t model, int stream_len, * @param setup pointer to the encoder setup * * @returns the bit length of the bitstream with the added encoded value on - * success; negative on error, CMP_ERROR_SAMLL_BUF if the bitstream buffer + * success; negative on error, CMP_ERROR_SMALL_BUF if the bitstream buffer * is too small to put the value in the bitstream * * @note no check if the data or model are in the allowed range @@ -653,7 +653,7 @@ static int encode_value_multi(uint32_t data, uint32_t model, int stream_len, * @param setup pointer to the encoder setup * * @returns the bit length of the bitstream with the added unencoded value on - * success; negative on error, CMP_ERROR_SAMLL_BUF if the bitstream buffer + * success; negative on error, CMP_ERROR_SMALL_BUF if the bitstream buffer * is too small to put the value in the bitstream * */ @@ -678,7 +678,7 @@ static int encode_value_none(uint32_t value, uint32_t unused, int stream_len, * @param setup pointer to the encoder setup * * @returns the bit length of the bitstream with the added encoded value on - * success; negative on error, CMP_ERROR_SAMLL_BUF if the bitstream buffer + * success; negative on error, CMP_ERROR_SMALL_BUF if the bitstream buffer * is too small to put the value in the bitstream, CMP_ERROR_HIGH_VALUE if * the value or the model is bigger than the max_used_bits parameter allows */ @@ -798,7 +798,7 @@ static int configure_encoder_setup(struct encoder_setupt *setup, * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream, CMP_ERROR_HIGH_VALUE if the value or the model is * bigger than the max_used_bits parameter allows */ @@ -887,7 +887,7 @@ static int compress_multi_entry_hdr(void **data, void **model, void **up_model, * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -959,7 +959,7 @@ static int compress_s_fx(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -1041,7 +1041,7 @@ static int compress_s_fx_efx(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -1129,7 +1129,7 @@ static int compress_s_fx_ncob(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -1244,7 +1244,7 @@ static int compress_s_fx_efx_ncob_ecob(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -1306,7 +1306,7 @@ static int compress_f_fx(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -1378,7 +1378,7 @@ static int compress_f_fx_efx(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -1456,7 +1456,7 @@ static int compress_f_fx_ncob(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -1560,7 +1560,7 @@ static int compress_f_fx_efx_ncob_ecob(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -1642,7 +1642,7 @@ static int compress_l_fx(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -1734,7 +1734,7 @@ static int compress_l_fx_efx(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -1850,7 +1850,7 @@ static int compress_l_fx_ncob(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -1991,7 +1991,7 @@ static int compress_l_fx_efx_ncob_ecob(const struct cmp_cfg *cfg) * * @param cfg pointer to the compression configuration structure * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -2062,7 +2062,7 @@ static int compress_nc_offset(const struct cmp_cfg *cfg) * * @param cfg pointer to the compression configuration structure * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -2144,7 +2144,7 @@ static int compress_nc_background(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -2176,11 +2176,11 @@ static int compress_smearing(const struct cmp_cfg *cfg) } err = configure_encoder_setup(&setup_mean, cfg->cmp_par_mean, cfg->spill_mean, - cfg->round, max_used_bits.smeating_mean, cfg); + cfg->round, max_used_bits.smearing_mean, cfg); if (err) return -1; err = configure_encoder_setup(&setup_var_mean, cfg->cmp_par_variance, cfg->spill_variance, - cfg->round, max_used_bits.smeating_variance_mean, cfg); + cfg->round, max_used_bits.smearing_variance_mean, cfg); if (err) return -1; err = configure_encoder_setup(&setup_pix, cfg->cmp_par_pixels_error, cfg->spill_pixels_error, @@ -2227,7 +2227,7 @@ static int compress_smearing(const struct cmp_cfg *cfg) * @param cmp_size length of the bitstream in bits * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -2277,6 +2277,8 @@ static int cmp_data_to_big_endian(const struct cmp_cfg *cfg, int cmp_size) return cmp_size; if (cfg->cmp_mode == CMP_MODE_RAW) { + if (s & 0x7) /* size must be a multiple of 8 in RAW mode */ + return -1; if (cmp_input_big_to_cpu_endianness(cfg->icu_output_buf, s/CHAR_BIT, cfg->data_type)) cmp_size = -1; @@ -2309,7 +2311,7 @@ static int cmp_data_to_big_endian(const struct cmp_cfg *cfg, int cmp_size) * started * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF (-2) if the compressed data buffer is too small to + * CMP_ERROR_SMALL_BUF (-2) if the compressed data buffer is too small to * hold the whole compressed data, CMP_ERROR_HIGH_VALUE (-3) if a data or * model value is bigger than the max_used_bits parameter allows (set with * the cmp_set_max_used_bits() function) @@ -2327,7 +2329,7 @@ int icu_compress_data(const struct cmp_cfg *cfg) if (raw_mode_is_used(cfg->cmp_mode)) if (cfg->samples > cfg->buffer_length) - return CMP_ERROR_SAMLL_BUF; + return CMP_ERROR_SMALL_BUF; if (!cmp_cfg_is_valid(cfg)) return -1; @@ -2402,11 +2404,13 @@ int icu_compress_data(const struct cmp_cfg *cfg) case DATA_TYPE_F_CAM_OFFSET: case DATA_TYPE_F_CAM_BACKGROUND: + /* LCOV_EXCL_START */ case DATA_TYPE_UNKNOWN: default: debug_print("Error: Data type not supported.\n"); cmp_size = -1; } + /* LCOV_EXCL_STOP */ } if (cfg->icu_output_buf) { diff --git a/lib/cmp_support.c b/lib/cmp_support.c index ae7941b..f157a6e 100644 --- a/lib/cmp_support.c +++ b/lib/cmp_support.c @@ -671,7 +671,7 @@ int cmp_cfg_imagette_is_valid(const struct cmp_cfg *cfg) return 0; if (!cmp_imagette_data_type_is_used(cfg->data_type)) { - debug_print("Error: The compression data type is not an imagette compression data type!\n"); + debug_print("Error: The compression data type is not an imagette compression data type.\n"); cfg_invalid++; } @@ -712,7 +712,7 @@ int cmp_cfg_fx_cob_is_valid(const struct cmp_cfg *cfg) return 0; if (!cmp_fx_cob_data_type_is_used(cfg->data_type)) { - debug_print("Error: The compression data type is not a flux/center of brightness compression data type.!\n"); + debug_print("Error: The compression data type is not a flux/center of brightness compression data type.\n"); cfg_invalid++; } /* flux parameter is needed for every fx_cob data_type */ @@ -816,7 +816,7 @@ int cmp_cfg_aux_is_valid(const struct cmp_cfg *cfg) return 0; if (!cmp_aux_data_type_is_used(cfg->data_type)) { - debug_print("Error: The compression data type is not an auxiliary science compression data type.!\n"); + debug_print("Error: The compression data type is not an auxiliary science compression data type.\n"); cfg_invalid++; } diff --git a/lib/decmp.c b/lib/decmp.c index 534fc46..f15c943 100644 --- a/lib/decmp.c +++ b/lib/decmp.c @@ -218,7 +218,7 @@ static decoder_ptr select_decoder(unsigned int golomb_par) * ignored if bitstream_adr is NULL * * @returns bit position of the last read bit in the bitstream on success; - * returns negative in case of erroneous input; returns CMP_ERROR_SAMLL_BUF + * returns negative in case of erroneous input; returns CMP_ERROR_SMALL_BUF * if the bitstream buffer is too small to read the value from the * bitstream */ @@ -248,7 +248,7 @@ static int get_n_bits32(uint32_t *p_value, unsigned int n_bits, int bit_offset, /* Check if bitstream buffer is large enough */ if ((unsigned int)stream_len > max_stream_len) { debug_print("Error: Buffer overflow detected.\n"); - return CMP_ERROR_SAMLL_BUF; + return CMP_ERROR_SMALL_BUF; } @@ -297,7 +297,7 @@ static int get_n_bits32(uint32_t *p_value, unsigned int n_bits, int bit_offset, * @param setup pointer to the decoder setup * * @returns bit index of the next code word in the bitstream on success; returns - * negative in case of erroneous input; returns CMP_ERROR_SAMLL_BUF if the + * negative in case of erroneous input; returns CMP_ERROR_SMALL_BUF if the * bitstream buffer is too small to read the value from the bitstream */ @@ -344,7 +344,7 @@ static int decode_normal(uint32_t *decoded_value, int stream_pos, * @param setup pointer to the decoder setup * * @returns bit index of the next code word in the bitstream on success; returns - * negative in case of erroneous input; returns CMP_ERROR_SAMLL_BUF if the + * negative in case of erroneous input; returns CMP_ERROR_SMALL_BUF if the * bitstream buffer is too small to read the value from the bitstream */ @@ -392,7 +392,7 @@ static int decode_zero(uint32_t *decoded_value, int stream_pos, * @param setup pointer to the decoder setup * * @returns bit index of the next code word in the bitstream on success; returns - * negative in case of erroneous input; returns CMP_ERROR_SAMLL_BUF if the + * negative in case of erroneous input; returns CMP_ERROR_SMALL_BUF if the * bitstream buffer is too small to read the value from the bitstream */ @@ -448,7 +448,7 @@ static uint32_t re_map_to_pos(uint32_t value_to_unmap) * @param setup pointer to the decoder setup * * @returns bit index of the next code word in the bitstream on success; returns - * negative in case of erroneous input; returns CMP_ERROR_SAMLL_BUF if the + * negative in case of erroneous input; returns CMP_ERROR_SMALL_BUF if the * bitstream buffer is too small to read the value from the bitstream */ @@ -597,7 +597,7 @@ static int decompress_multi_entry_hdr(void **data, void **model, void **up_model * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -669,7 +669,7 @@ static int decompress_s_fx(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -752,7 +752,7 @@ static int decompress_s_fx_efx(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -843,7 +843,7 @@ static int decompress_s_fx_ncob(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -964,7 +964,7 @@ static int decompress_s_fx_efx_ncob_ecob(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -1024,7 +1024,7 @@ static int decompress_f_fx(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -1096,7 +1096,7 @@ static int decompress_f_fx_efx(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -1176,7 +1176,7 @@ static int decompress_f_fx_ncob(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -1286,7 +1286,7 @@ static int decompress_f_fx_efx_ncob_ecob(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -1369,7 +1369,7 @@ static int decompress_l_fx(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -1463,7 +1463,7 @@ static int decompress_l_fx_efx(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -1585,7 +1585,7 @@ static int decompress_l_fx_ncob(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -1731,7 +1731,7 @@ static int decompress_l_fx_efx_ncob_ecob(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -1803,7 +1803,7 @@ static int decompress_nc_offset(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -1886,7 +1886,7 @@ static int decompress_nc_background(const struct cmp_cfg *cfg) * @param cfg pointer to the compression configuration structure * * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SAMLL_BUF if the bitstream buffer is too small to put the + * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the * value in the bitstream */ @@ -1917,10 +1917,10 @@ static int decompress_smearing(const struct cmp_cfg *cfg) } if (configure_decoder_setup(&setup_mean, cfg->cmp_par_mean, cfg->spill_mean, - cfg->round, max_used_bits.smeating_mean, cfg)) + cfg->round, max_used_bits.smearing_mean, cfg)) return -1; if (configure_decoder_setup(&setup_var, cfg->cmp_par_variance, cfg->spill_variance, - cfg->round, max_used_bits.smeating_variance_mean, cfg)) + cfg->round, max_used_bits.smearing_variance_mean, cfg)) return -1; if (configure_decoder_setup(&setup_pix, cfg->cmp_par_pixels_error, cfg->spill_pixels_error, cfg->round, max_used_bits.smearing_outlier_pixels, cfg)) diff --git a/test/cmp_icu/test_cmp_icu.c b/test/cmp_icu/test_cmp_icu.c index 9038a39..3eae4be 100644 --- a/test/cmp_icu/test_cmp_icu.c +++ b/test/cmp_icu/test_cmp_icu.c @@ -31,7 +31,7 @@ void setUp(void) #endif if (!n) { - n++; + n = 1; srand(seed); printf("seed: %u\n", seed); } @@ -41,20 +41,18 @@ void setUp(void) /** * @brief generate a random number * - * @param min minimum value - * @param max maximum value + * @param min minimum value (inclusive) + * @param max maximum value (inclusive) * * @returns "random" numbers in the range [M, N] * * @see https://c-faq.com/lib/randrange.html */ -int random_range(unsigned int min, unsigned int max) +int random_between(unsigned int min, unsigned int max) { - if (min > max) - TEST_ASSERT(0); - if (max-min > RAND_MAX) - TEST_ASSERT(0); + TEST_ASSERT(min < max); + TEST_ASSERT(max-min <= RAND_MAX); return min + rand() / (RAND_MAX / (max - min + 1) + 1); } @@ -170,10 +168,10 @@ void test_cmp_cfg_icu_create(void) TEST_ASSERT_EQUAL_INT(3, cfg.round); /* random test */ - data_type = random_range(DATA_TYPE_IMAGETTE, biggest_data_type); - cmp_mode = random_range(CMP_MODE_RAW, CMP_MODE_STUFF); - model_value = random_range(0, MAX_MODEL_VALUE); - lossy_par = random_range(CMP_LOSSLESS, MAX_ICU_ROUND); + data_type = random_between(DATA_TYPE_IMAGETTE, biggest_data_type); + cmp_mode = random_between(CMP_MODE_RAW, CMP_MODE_STUFF); + model_value = random_between(0, MAX_MODEL_VALUE); + lossy_par = random_between(CMP_LOSSLESS, MAX_ICU_ROUND); cfg = cmp_cfg_icu_create(data_type, cmp_mode, model_value, lossy_par); TEST_ASSERT_EQUAL_INT(data_type, cfg.data_type); TEST_ASSERT_EQUAL_INT(cmp_mode, cfg.cmp_mode); @@ -1610,7 +1608,7 @@ void test_put_n_bits32(void) /* try to put too much in the bitstream */ v = 0x1; n = 1; o = 96; rval = put_n_bits32(v, n, o, testarray0, l); - TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, rval); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SMALL_BUF, rval); TEST_ASSERT(testarray0[0] == 0); TEST_ASSERT(testarray0[1] == 0); TEST_ASSERT(testarray0[2] == 0); @@ -1622,7 +1620,7 @@ void test_put_n_bits32(void) /* offset lager than max_stream_len(l) */ v = 0x0; n = 32; o = INT32_MAX; rval = put_n_bits32(v, n, o, testarray1, l); - TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, rval); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SMALL_BUF, rval); TEST_ASSERT(testarray1[0] == 0xffffffff); TEST_ASSERT(testarray1[1] == 0xffffffff); TEST_ASSERT(testarray1[2] == 0xffffffff); @@ -1887,7 +1885,7 @@ void test_encode_value_zero(void) /* small buffer error */ data = 23; model = 26; stream_len = encode_value_zero(data, model, stream_len, &setup); - TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, stream_len); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SMALL_BUF, stream_len); /* reset bitstream to all bits set */ bitstream[0] = ~0U; @@ -1938,7 +1936,7 @@ void test_encode_value_zero(void) setup.max_stream_len = 32; data = 31; model = 0; stream_len = encode_value_zero(data, model, stream_len, &setup); - TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, stream_len); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SMALL_BUF, stream_len); TEST_ASSERT_EQUAL_HEX(0, bitstream[0]); TEST_ASSERT_EQUAL_HEX(0, bitstream[1]); TEST_ASSERT_EQUAL_HEX(0, bitstream[2]); @@ -2021,7 +2019,7 @@ void test_encode_value_multi(void) /* small buffer error */ data = 0; model = 38; stream_len = encode_value_multi(data, model, stream_len, &setup); - TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, stream_len); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SMALL_BUF, stream_len); /* small buffer error when creating the multi escape symbol*/ bitstream[0] = 0; @@ -2031,7 +2029,7 @@ void test_encode_value_multi(void) stream_len = 32; data = 31; model = 0; stream_len = encode_value_multi(data, model, stream_len, &setup); - TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, stream_len); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SMALL_BUF, stream_len); TEST_ASSERT_EQUAL_HEX(0, bitstream[0]); TEST_ASSERT_EQUAL_HEX(0, bitstream[1]); } @@ -2095,7 +2093,7 @@ void test_encode_value(void) /* small buffer error bitstream can not hold more data*/ cmp_size = encode_value(data, model, cmp_size, &setup); - TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, cmp_size); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SMALL_BUF, cmp_size); /* reset bitstream */ bitstream[0] = 0; @@ -2187,8 +2185,8 @@ void test_cmp_get_max_used_bits(void) TEST_ASSERT_EQUAL_INT(max_used_bits.nc_background_variance, MAX_USED_NC_BACKGROUND_VARIANCE_BITS); TEST_ASSERT_EQUAL_INT(max_used_bits.nc_background_outlier_pixels, MAX_USED_NC_BACKGROUND_OUTLIER_PIXELS_BITS); - TEST_ASSERT_EQUAL_INT(max_used_bits.smeating_mean, MAX_USED_SMEARING_MEAN_BITS); - TEST_ASSERT_EQUAL_INT(max_used_bits.smeating_variance_mean, MAX_USED_SMEARING_VARIANCE_MEAN_BITS); + TEST_ASSERT_EQUAL_INT(max_used_bits.smearing_mean, MAX_USED_SMEARING_MEAN_BITS); + TEST_ASSERT_EQUAL_INT(max_used_bits.smearing_variance_mean, MAX_USED_SMEARING_VARIANCE_MEAN_BITS); TEST_ASSERT_EQUAL_INT(max_used_bits.smearing_outlier_pixels, MAX_USED_SMEARING_OUTLIER_PIXELS_BITS); TEST_ASSERT_EQUAL_INT(max_used_bits.fc_offset_mean, MAX_USED_FC_OFFSET_MEAN_BITS); @@ -2553,7 +2551,7 @@ void test_compress_imagette_raw(void) cfg.buffer_length = 6; /* the buffer is to small */ cmp_size = icu_compress_data(&cfg); - TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, cmp_size); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SMALL_BUF, cmp_size); } @@ -2592,7 +2590,7 @@ void test_compress_imagette_error_cases(void) cfg.buffer_length = 4; cmp_size = icu_compress_data(&cfg); - TEST_ASSERT_EQUAL_INT(CMP_ERROR_SAMLL_BUF, cmp_size); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SMALL_BUF, cmp_size); /* error in setup */ @@ -2902,7 +2900,8 @@ void test_compress_s_fx_model_multi(void) } -void test_compress_s_fx_efx_model_multi(void) +#if 0 +void todo_est_compress_s_fx_efx_model_multi(void) { uint32_t i; struct s_fx_efx data[6], model[6]; @@ -3014,6 +3013,1347 @@ void test_compress_s_fx_efx_model_multi(void) free(cfg.icu_new_model_buf); free(cfg.icu_output_buf); } +#endif + + +/** + * @test compress_s_fx + */ + +void test_compress_s_fx_error_cases(void) +{ + int error, cmp_bits, compressed_data_size; + struct cmp_cfg cfg = {0}; + uint32_t cmp_par_exp_flags = MAX_ICU_GOLOMB_PAR; + uint32_t spillover_exp_flags = 6; + uint32_t cmp_par_fx = 2; + uint32_t spillover_fx = 8; + uint8_t data_to_compress[MULTI_ENTRY_HDR_SIZE+3*sizeof(struct s_fx)] = {0}; + uint8_t compressed_data[MULTI_ENTRY_HDR_SIZE+1*sizeof(struct s_fx)] = {0}; + struct cmp_max_used_bits max_used_bits = {0}; + struct s_fx *data_p = (struct s_fx *)&data_to_compress[MULTI_ENTRY_HDR_SIZE]; + + max_used_bits.s_exp_flags = 2; + max_used_bits.s_fx = 21; + cmp_set_max_used_bits(&max_used_bits); + + cfg = cmp_cfg_icu_create(DATA_TYPE_S_FX, CMP_MODE_DIFF_ZERO, 0, CMP_LOSSLESS); + TEST_ASSERT(cfg.data_type != DATA_TYPE_UNKNOWN); + + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, CMP_PAR_UNUSED, + CMP_PAR_UNUSED, CMP_PAR_UNUSED, CMP_PAR_UNUSED, + CMP_PAR_UNUSED, CMP_PAR_UNUSED, CMP_PAR_UNUSED, + CMP_PAR_UNUSED); + TEST_ASSERT_FALSE(error); + + compressed_data_size = cmp_cfg_icu_buffers(&cfg, data_to_compress, 3, NULL, + NULL, (uint32_t *)compressed_data, 1); + TEST_ASSERT_EQUAL_INT(sizeof(compressed_data), compressed_data_size); + + /* test if data are higher than max used bits value */ + data_p[0].fx = 0x200000; /* has more than 21 bits (max_used_bits.s_fx) */ + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + /* compressed data are to small for the compressed_data buffer */ + max_used_bits.s_exp_flags = 8; + max_used_bits.s_fx= 32; + cmp_set_max_used_bits(&max_used_bits); + memset(data_to_compress, 0xff, sizeof(data_to_compress)); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SMALL_BUF, cmp_bits); + + max_used_bits.s_exp_flags = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.s_exp_flags = 32; + max_used_bits.s_fx= 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); +} + + +/** + * @test compress_s_fx_efx + */ + +void test_compress_s_fx_efx_error_cases(void) +{ + int error, cmp_bits, compressed_data_size; + struct cmp_cfg cfg = {0}; + uint32_t cmp_par_exp_flags = 2; + uint32_t spillover_exp_flags = 6; + uint32_t cmp_par_fx = 1; + uint32_t spillover_fx = 8; + uint32_t cmp_par_efx = MAX_ICU_GOLOMB_PAR; + uint32_t spillover_efx = cmp_icu_max_spill(MAX_ICU_GOLOMB_PAR); + uint8_t data_to_compress[MULTI_ENTRY_HDR_SIZE+2*sizeof(struct s_fx_efx)] = {0}; + uint8_t compressed_data[MULTI_ENTRY_HDR_SIZE+1*sizeof(struct s_fx_efx)] = {0}; + struct cmp_max_used_bits max_used_bits = {0}; + struct s_fx_efx *data_p= (struct s_fx_efx *)&data_to_compress[MULTI_ENTRY_HDR_SIZE]; + + max_used_bits.s_exp_flags = 2; + max_used_bits.s_fx = 21; + max_used_bits.s_efx = 16; + cmp_set_max_used_bits(&max_used_bits); + + cfg = cmp_cfg_icu_create(DATA_TYPE_S_FX_EFX, CMP_MODE_DIFF_MULTI, 0, CMP_LOSSLESS); + TEST_ASSERT(cfg.data_type != DATA_TYPE_UNKNOWN); + + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, CMP_PAR_UNUSED, + CMP_PAR_UNUSED, cmp_par_efx, spillover_efx, + CMP_PAR_UNUSED, CMP_PAR_UNUSED, CMP_PAR_UNUSED, + CMP_PAR_UNUSED); + TEST_ASSERT_FALSE(error); + + compressed_data_size = cmp_cfg_icu_buffers(&cfg, data_to_compress, 2, NULL, + NULL, (uint32_t *)compressed_data, 1); + TEST_ASSERT_EQUAL_INT(sizeof(compressed_data), compressed_data_size); + + /* test if data are higher than max used bits value */ + data_p[0].exp_flags = 0x4; /* has more than 2 bits (max_used_bits.s_exp_flags) */ + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[0].exp_flags = 0x3; + data_p[1].fx = 0x200000; /* has more than 21 bits (max_used_bits.fx) */ + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[1].fx = 0x1FFFFF; + data_p[1].efx = 0x100000; /* has more than 16 bits (max_used_bits.efx) */ + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + /* error case exp_flag setup */ + max_used_bits.s_exp_flags = 33; + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + /* error case fx setup */ + max_used_bits.s_exp_flags = 2; + max_used_bits.s_fx = 33; + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + /* error case efx setup */ + max_used_bits.s_fx = 21; + max_used_bits.s_efx = 33; + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); +} + + +/** + * @test compress_s_fx_ncob + */ + +void test_compress_s_fx_ncob_error_cases(void) +{ + int error, cmp_bits, compressed_data_size; + struct cmp_cfg cfg = {0}; + uint32_t cmp_par_exp_flags = 3; + uint32_t spillover_exp_flags = 6; + uint32_t cmp_par_fx = 1; + uint32_t spillover_fx = 8; + uint32_t cmp_par_ncob = MAX_ICU_GOLOMB_PAR; + uint32_t spillover_ncob = cmp_icu_max_spill(MAX_ICU_GOLOMB_PAR); + uint8_t data_to_compress[MULTI_ENTRY_HDR_SIZE+3*sizeof(struct s_fx_ncob)] = {0}; + uint8_t model_data[MULTI_ENTRY_HDR_SIZE+3*sizeof(struct s_fx_ncob)] = {0}; + uint8_t compressed_data[MULTI_ENTRY_HDR_SIZE+1*sizeof(struct s_fx_ncob)] = {0}; + struct cmp_max_used_bits max_used_bits = {0}; + struct s_fx_ncob *data_p = (struct s_fx_ncob *)&data_to_compress[MULTI_ENTRY_HDR_SIZE]; + + + max_used_bits.s_exp_flags = 2; + max_used_bits.s_fx = 21; + max_used_bits.s_ncob = 31; + cmp_set_max_used_bits(&max_used_bits); + + cfg = cmp_cfg_icu_create(DATA_TYPE_S_FX_NCOB, CMP_MODE_MODEL_ZERO, 0, CMP_LOSSLESS); + TEST_ASSERT(cfg.data_type != DATA_TYPE_UNKNOWN); + + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + CMP_PAR_UNUSED, CMP_PAR_UNUSED, CMP_PAR_UNUSED, + CMP_PAR_UNUSED, CMP_PAR_UNUSED, CMP_PAR_UNUSED); + TEST_ASSERT_FALSE(error); + + compressed_data_size = cmp_cfg_icu_buffers(&cfg, data_to_compress, 3, model_data, + NULL, (uint32_t *)compressed_data, 1); + TEST_ASSERT_EQUAL_INT(sizeof(compressed_data), compressed_data_size); + + /* the compressed_data buffer is to small */ + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SMALL_BUF, cmp_bits); + + /* test if data are higher than max used bits value */ + data_p[2].exp_flags = 0x4; /* has more than 2 bits (max_used_bits.s_exp_flags) */ + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[2].exp_flags = 0x3; + data_p[1].fx = 0x200000; /* value to high */ + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[1].fx = 0x1FFFFF; /* value to high */ + data_p[0].ncob_y = 0x80000000; /* value to high */ + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + data_p[0].ncob_y = 0x7FFFFFFF; /* value to high */ + + /* error case exp_flag setup */ + max_used_bits.s_exp_flags = 33; + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + /* error case fx setup */ + max_used_bits.s_exp_flags = 2; + max_used_bits.s_fx = 33; + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + /* error case efx setup */ + max_used_bits.s_fx = 21; + max_used_bits.s_ncob = 33; + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); +} + + +/** + * @test compress_s_fx_efx_ncob_ecob + */ + +void test_compress_s_fx_efx_ncob_ecob_error_cases(void) +{ + int error, cmp_bits, compressed_data_size; + struct cmp_cfg cfg = {0}; + uint32_t cmp_par_exp_flags = 3; + uint32_t spillover_exp_flags = 6; + uint32_t cmp_par_fx = 1; + uint32_t spillover_fx = 8; + uint32_t cmp_par_ncob = MAX_ICU_GOLOMB_PAR; + uint32_t spillover_ncob = cmp_icu_max_spill(MAX_ICU_GOLOMB_PAR); + uint32_t cmp_par_efx = MAX_ICU_GOLOMB_PAR; + uint32_t spillover_efx = cmp_icu_max_spill(MAX_ICU_GOLOMB_PAR); + uint32_t cmp_par_ecob = 23; + uint32_t spillover_ecob = cmp_icu_max_spill(23); + uint8_t data_to_compress[MULTI_ENTRY_HDR_SIZE+3*sizeof(struct s_fx_efx_ncob_ecob)] = {0}; + uint8_t model_data[MULTI_ENTRY_HDR_SIZE+3*sizeof(struct s_fx_efx_ncob_ecob)] = {0}; + uint8_t compressed_data[MULTI_ENTRY_HDR_SIZE+1*sizeof(struct s_fx_efx_ncob_ecob)] = {0}; + struct cmp_max_used_bits max_used_bits = {0}; + struct s_fx_efx_ncob_ecob *data_p = (struct s_fx_efx_ncob_ecob *)&data_to_compress[MULTI_ENTRY_HDR_SIZE]; + + + max_used_bits.s_exp_flags = 2; + max_used_bits.s_fx = 21; + max_used_bits.s_ncob = 31; + max_used_bits.s_efx = 23; + max_used_bits.s_ecob = 7; + cmp_set_max_used_bits(&max_used_bits); + + cfg = cmp_cfg_icu_create(DATA_TYPE_S_FX_EFX_NCOB_ECOB, CMP_MODE_MODEL_ZERO, 0, CMP_LOSSLESS); + TEST_ASSERT(cfg.data_type != DATA_TYPE_UNKNOWN); + + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, cmp_par_ncob, + spillover_ncob, cmp_par_efx, spillover_efx, + cmp_par_ecob, spillover_ecob, CMP_PAR_UNUSED, CMP_PAR_UNUSED); + + TEST_ASSERT_FALSE(error); + + compressed_data_size = cmp_cfg_icu_buffers(&cfg, data_to_compress, 3, model_data, + NULL, (uint32_t *)compressed_data, 1); + TEST_ASSERT_EQUAL_INT(sizeof(compressed_data), compressed_data_size); + + /* the compressed_data buffer is to small */ + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SMALL_BUF, cmp_bits); + + /* test if data are higher than max used bits value */ + data_p[2].exp_flags = 0x4; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[2].exp_flags = 0x3; + data_p[2].fx = 0x200000; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[2].fx = 0x1FFFFF; + data_p[1].ncob_x = 0x80000000; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[1].ncob_x = 0x7FFFFFFF; + data_p[1].ncob_y = 0x80000000; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[1].ncob_y = 0x7FFFFFFF; + data_p[1].efx = 0x800000; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[1].efx = 0x7FFFFF; + data_p[1].ecob_y = 0x80; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + data_p[1].ecob_y = 0x7F; + + /* error case exp_flag setup */ + max_used_bits.s_exp_flags = 33; + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + /* error case fx setup */ + max_used_bits.s_exp_flags = 32; + max_used_bits.s_fx = 33; + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + /* error case efx setup */ + max_used_bits.s_fx = 32; + max_used_bits.s_ncob = 33; + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.s_ncob = 32; + max_used_bits.s_efx = 33; + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.s_efx = 32; + max_used_bits.s_ecob = 33; + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + max_used_bits.s_ecob = 32; +} + + +/** + * @test compress_f_fx + */ + +void test_compress_f_fx_error_cases(void) +{ + int error, cmp_bits, compressed_data_size; + struct cmp_cfg cfg = {0}; + uint32_t cmp_par_fx = MAX_ICU_GOLOMB_PAR; + uint32_t spillover_fx = 8; + uint8_t data_to_compress[MULTI_ENTRY_HDR_SIZE+3*sizeof(struct f_fx)] = {0}; + uint8_t compressed_data[MULTI_ENTRY_HDR_SIZE+1*sizeof(struct f_fx)] = {0}; + struct cmp_max_used_bits max_used_bits = {0}; + + max_used_bits.f_fx = 23; + cmp_set_max_used_bits(&max_used_bits); + + cfg = cmp_cfg_icu_create(DATA_TYPE_F_FX, CMP_MODE_DIFF_ZERO, 0, CMP_LOSSLESS); + TEST_ASSERT(cfg.data_type != DATA_TYPE_UNKNOWN); + + error = cmp_cfg_fx_cob(&cfg, CMP_PAR_UNUSED, CMP_PAR_UNUSED, + cmp_par_fx, spillover_fx, CMP_PAR_UNUSED, + CMP_PAR_UNUSED, CMP_PAR_UNUSED, CMP_PAR_UNUSED, + CMP_PAR_UNUSED, CMP_PAR_UNUSED, CMP_PAR_UNUSED, + CMP_PAR_UNUSED); + TEST_ASSERT_FALSE(error); + + compressed_data_size = cmp_cfg_icu_buffers(&cfg, data_to_compress, 3, NULL, NULL, (uint32_t *)compressed_data, 1); + TEST_ASSERT_EQUAL_INT(sizeof(compressed_data), compressed_data_size); + + /* compressed data are to small for the compressed_data buffer */ + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SMALL_BUF, cmp_bits); + + max_used_bits.f_fx= 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); +} + + +/** + * @test compress_f_fx_efx + */ + +void test_compress_f_fx_efx_error_cases(void) +{ + int error, cmp_bits, compressed_data_size; + struct cmp_cfg cfg = {0}; + uint32_t cmp_par_fx = 1; + uint32_t spillover_fx = 8; + uint32_t cmp_par_efx = 1; + uint32_t spillover_efx = 8; + uint8_t data_to_compress[MULTI_ENTRY_HDR_SIZE+2*sizeof(struct f_fx_efx)] = {0}; + uint8_t compressed_data[MULTI_ENTRY_HDR_SIZE+1*sizeof(struct f_fx_efx)] = {0}; + struct cmp_max_used_bits max_used_bits = {0}; + struct f_fx_efx *data_p = (struct f_fx_efx *)&data_to_compress[MULTI_ENTRY_HDR_SIZE]; + + max_used_bits.f_fx = 23; + max_used_bits.f_efx = 31; + cmp_set_max_used_bits(&max_used_bits); + + cfg = cmp_cfg_icu_create(DATA_TYPE_F_FX_EFX, CMP_MODE_DIFF_ZERO, 0, CMP_LOSSLESS); + TEST_ASSERT(cfg.data_type != DATA_TYPE_UNKNOWN); + + error = cmp_cfg_fx_cob(&cfg, CMP_PAR_UNUSED, CMP_PAR_UNUSED, + cmp_par_fx, spillover_fx, CMP_PAR_UNUSED, + CMP_PAR_UNUSED, cmp_par_efx, spillover_efx, + CMP_PAR_UNUSED, CMP_PAR_UNUSED, CMP_PAR_UNUSED, + CMP_PAR_UNUSED); + TEST_ASSERT_FALSE(error); + + compressed_data_size = cmp_cfg_icu_buffers(&cfg, data_to_compress, 2, NULL, NULL, + (uint32_t *)compressed_data, 1); + TEST_ASSERT_EQUAL_INT(sizeof(compressed_data), compressed_data_size); + + /* compressed data are to small for the compressed_data buffer */ + data_p[0].fx = 42; + data_p[0].efx = 42; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SMALL_BUF, cmp_bits); + + /* efx value is to big for the max used bits values */ + data_p[0].efx = 0x80000000; + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + data_p[0].efx = 0x7FFFFFFF; + + max_used_bits.f_fx= 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.f_fx= 32; + max_used_bits.f_efx= 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); +} + + +/** + * @test compress_f_fx_ncob + */ + +void test_compress_f_fx_ncob_error_cases(void) +{ + int error, cmp_bits, compressed_data_size; + struct cmp_cfg cfg = {0}; + uint32_t cmp_par_fx = MAX_ICU_GOLOMB_PAR; + uint32_t spillover_fx = 8; + uint32_t cmp_par_ncob = 1; + uint32_t spillover_ncob = 8; + uint8_t data_to_compress[MULTI_ENTRY_HDR_SIZE+2*sizeof(struct f_fx_ncob)] = {0}; + uint8_t compressed_data[MULTI_ENTRY_HDR_SIZE+1*sizeof(struct f_fx_ncob)] = {0}; + struct cmp_max_used_bits max_used_bits = {0}; + struct f_fx_ncob *data_p = (struct f_fx_ncob *)&data_to_compress[MULTI_ENTRY_HDR_SIZE]; + + max_used_bits.f_fx = 31; + max_used_bits.f_ncob = 23; + cmp_set_max_used_bits(&max_used_bits); + + cfg = cmp_cfg_icu_create(DATA_TYPE_F_FX_NCOB, CMP_MODE_DIFF_ZERO, 0, CMP_LOSSLESS); + TEST_ASSERT(cfg.data_type != DATA_TYPE_UNKNOWN); + + error = cmp_cfg_fx_cob(&cfg, CMP_PAR_UNUSED, CMP_PAR_UNUSED, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + CMP_PAR_UNUSED, CMP_PAR_UNUSED, CMP_PAR_UNUSED, + CMP_PAR_UNUSED, CMP_PAR_UNUSED, CMP_PAR_UNUSED); + TEST_ASSERT_FALSE(error); + + compressed_data_size = cmp_cfg_icu_buffers(&cfg, data_to_compress, 2, NULL, NULL, + (uint32_t *)compressed_data, 1); + TEST_ASSERT_EQUAL_INT(sizeof(compressed_data), compressed_data_size); + + /* compressed data are to small for the compressed_data buffer */ + data_p[0].fx = 42; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SMALL_BUF, cmp_bits); + + /* value is to big for the max used bits values */ + data_p[0].ncob_x = 0x800000; + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + data_p[0].ncob_x = 0x7FFFFF; + data_p[0].ncob_y = 0x800000; + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + data_p[0].ncob_y = 0x7FFFFF; + + max_used_bits.f_fx= 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.f_fx= 32; + max_used_bits.f_ncob= 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); +} + + +/** + * @test compress_f_fx_efx_ncob_ecob + */ + +void test_compress_f_fx_efx_ncob_ecob(void) +{ + int error, cmp_bits, compressed_data_size; + struct cmp_cfg cfg = {0}; + uint32_t cmp_par_fx = 1; + uint32_t spillover_fx = 8; + uint32_t cmp_par_ncob = 2; + uint32_t spillover_ncob = 10; + uint32_t cmp_par_efx = 3; + uint32_t spillover_efx = 44; + uint32_t cmp_par_ecob = 5; + uint32_t spillover_ecob = 55; + uint8_t data_to_compress[MULTI_ENTRY_HDR_SIZE+4*sizeof(struct f_fx_efx_ncob_ecob)] = {0}; + uint8_t compressed_data[MULTI_ENTRY_HDR_SIZE+1*sizeof(struct f_fx_efx_ncob_ecob)] = {0}; + struct cmp_max_used_bits max_used_bits = {0}; + struct f_fx_efx_ncob_ecob *data_p = (struct f_fx_efx_ncob_ecob *)&data_to_compress[MULTI_ENTRY_HDR_SIZE]; + + max_used_bits.f_fx = 31; + max_used_bits.f_ncob = 3; + max_used_bits.f_efx = 16; + max_used_bits.f_ecob = 8; + cmp_set_max_used_bits(&max_used_bits); + + cfg = cmp_cfg_icu_create(DATA_TYPE_F_FX_EFX_NCOB_ECOB, CMP_MODE_DIFF_ZERO, 0, CMP_LOSSLESS); + TEST_ASSERT(cfg.data_type != DATA_TYPE_UNKNOWN); + + error = cmp_cfg_fx_cob(&cfg, CMP_PAR_UNUSED, CMP_PAR_UNUSED, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + cmp_par_efx, spillover_efx, cmp_par_ecob, spillover_ecob, + CMP_PAR_UNUSED, CMP_PAR_UNUSED); + TEST_ASSERT_FALSE(error); + + compressed_data_size = cmp_cfg_icu_buffers(&cfg, data_to_compress, 4, NULL, NULL, + (uint32_t *)compressed_data, 1); + TEST_ASSERT_EQUAL_INT(sizeof(compressed_data), compressed_data_size); + + /* value is to big for the max used bits values */ + data_p[3].fx = 0x80000000; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[3].fx = 0x80000000-1; + data_p[2].ncob_x = 0x8; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[2].ncob_x = 0x7; + data_p[1].ncob_y = 0x8; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[1].ncob_y = 0x7; + data_p[0].efx = 0x10000; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[0].efx = 0x10000-1; + data_p[2].ecob_x = 0x100; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[2].ecob_x = 0x100-1; + data_p[3].ecob_y = 0x100; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + data_p[3].ecob_y = 0x100-1; + + max_used_bits.f_fx = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.f_fx = 32; + max_used_bits.f_ncob = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.f_ncob = 32; + max_used_bits.f_efx = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.f_efx = 32; + max_used_bits.f_ecob = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); +} + + +/** + * @test compress_l_fx + */ + +void test_compress_l_fx_error_cases(void) +{ + int error, cmp_bits, compressed_data_size; + struct cmp_cfg cfg = {0}; + uint32_t cmp_par_exp_flags = 3; + uint32_t spillover_exp_flags = 10; + uint32_t cmp_par_fx = 1; + uint32_t spillover_fx = 8; + uint32_t cmp_par_fx_cob_variance = 30; + uint32_t spillover_fx_cob_variance = 8; + uint8_t data_to_compress[MULTI_ENTRY_HDR_SIZE+3*sizeof(struct l_fx)] = {0}; + uint8_t compressed_data[MULTI_ENTRY_HDR_SIZE+1*sizeof(struct l_fx)] = {0}; + struct cmp_max_used_bits max_used_bits = {0}; + struct l_fx *data_p = (struct l_fx *)&data_to_compress[MULTI_ENTRY_HDR_SIZE]; + + max_used_bits.l_exp_flags = 23; + max_used_bits.l_fx = 31; + max_used_bits.l_efx = 1; + max_used_bits.l_fx_variance = 23; + cmp_set_max_used_bits(&max_used_bits); + + cfg = cmp_cfg_icu_create(DATA_TYPE_L_FX, CMP_MODE_DIFF_ZERO, 0, CMP_LOSSLESS); + TEST_ASSERT(cfg.data_type != DATA_TYPE_UNKNOWN); + + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, CMP_PAR_UNUSED, CMP_PAR_UNUSED, + CMP_PAR_UNUSED, CMP_PAR_UNUSED, CMP_PAR_UNUSED, CMP_PAR_UNUSED, + cmp_par_fx_cob_variance, spillover_fx_cob_variance); + TEST_ASSERT_FALSE(error); + + compressed_data_size = cmp_cfg_icu_buffers(&cfg, data_to_compress, 3, NULL, NULL, + (uint32_t *)compressed_data, 1); + TEST_ASSERT_EQUAL_INT(sizeof(compressed_data), compressed_data_size); + + /* value is to big for the max used bits values */ + data_p[2].exp_flags = 0x800000; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[2].exp_flags = 0x800000-1; + data_p[2].fx = 0x80000000; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[2].fx = 0x80000000-1; + data_p[0].fx_variance = 0x800000; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[0].fx_variance = 0x800000-1; + + max_used_bits.l_exp_flags = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.l_exp_flags = 32; + max_used_bits.l_fx = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.l_fx = 32; + max_used_bits.l_fx_variance = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); +} + + +/** + * @test compress_l_fx_efx + */ + +void test_compress_l_fx_efx_error_cases(void) +{ + int error, cmp_bits, compressed_data_size; + struct cmp_cfg cfg = {0}; + uint32_t cmp_par_exp_flags = MAX_ICU_GOLOMB_PAR; + uint32_t spillover_exp_flags = cmp_icu_max_spill(cmp_par_exp_flags); + uint32_t cmp_par_fx = 1; + uint32_t spillover_fx = 8; + uint32_t cmp_par_efx = 3; + uint32_t spillover_efx = 44; + uint32_t cmp_par_fx_cob_variance = 30; + uint32_t spillover_fx_cob_variance = 8; + uint8_t data_to_compress[MULTI_ENTRY_HDR_SIZE+3*sizeof(struct l_fx_efx)] = {0}; + uint8_t compressed_data[MULTI_ENTRY_HDR_SIZE+1*sizeof(struct l_fx_efx)] = {0}; + struct cmp_max_used_bits max_used_bits = {0}; + struct l_fx_efx *data_p = (struct l_fx_efx *)&data_to_compress[MULTI_ENTRY_HDR_SIZE]; + + max_used_bits.l_exp_flags = 23; + max_used_bits.l_fx = 31; + max_used_bits.l_efx = 1; + max_used_bits.l_fx_variance = 23; + cmp_set_max_used_bits(&max_used_bits); + + cfg = cmp_cfg_icu_create(DATA_TYPE_L_FX_EFX, CMP_MODE_DIFF_ZERO, 0, CMP_LOSSLESS); + TEST_ASSERT(cfg.data_type != DATA_TYPE_UNKNOWN); + + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, CMP_PAR_UNUSED, CMP_PAR_UNUSED, + cmp_par_efx, spillover_efx, CMP_PAR_UNUSED, CMP_PAR_UNUSED, + cmp_par_fx_cob_variance, spillover_fx_cob_variance); + TEST_ASSERT_FALSE(error); + + compressed_data_size = cmp_cfg_icu_buffers(&cfg, data_to_compress, 3, NULL, NULL, + (uint32_t *)compressed_data, 1); + TEST_ASSERT_EQUAL_INT(sizeof(compressed_data), compressed_data_size); + + /* value is to big for the max used bits values */ + data_p[2].exp_flags = 0x800000; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[2].exp_flags = 0x800000-1; + data_p[2].fx = 0x80000000; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[2].fx = 0x80000000-1; + data_p[1].efx = 0x2; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[1].efx = 0x1; + data_p[0].fx_variance = 0x800000; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[0].fx_variance = 0x800000-1; + + max_used_bits.l_exp_flags = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.l_exp_flags = 32; + max_used_bits.l_fx = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.l_fx = 32; + max_used_bits.l_efx = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.l_efx = 32; + max_used_bits.l_fx_variance = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); +} + + +/** + * @test compress_l_fx_ncob + */ + +void test_compress_l_fx_ncob_error_cases(void) +{ + int error, cmp_bits, compressed_data_size; + struct cmp_cfg cfg = {0}; + uint32_t cmp_par_exp_flags = MAX_ICU_GOLOMB_PAR; + uint32_t spillover_exp_flags = cmp_icu_max_spill(cmp_par_exp_flags); + uint32_t cmp_par_fx = 1; + uint32_t spillover_fx = 8; + uint32_t cmp_par_ncob = 2; + uint32_t spillover_ncob = 10; + uint32_t cmp_par_fx_cob_variance = 30; + uint32_t spillover_fx_cob_variance = 8; + uint8_t data_to_compress[MULTI_ENTRY_HDR_SIZE+3*sizeof(struct l_fx_ncob)] = {0}; + uint8_t compressed_data[MULTI_ENTRY_HDR_SIZE+1*sizeof(struct l_fx_ncob)] = {0}; + struct cmp_max_used_bits max_used_bits = {0}; + struct l_fx_ncob *data_p = (struct l_fx_ncob *)&data_to_compress[MULTI_ENTRY_HDR_SIZE]; + + max_used_bits.l_exp_flags = 23; + max_used_bits.l_fx = 31; + max_used_bits.l_ncob = 2; + max_used_bits.l_fx_variance = 23; + max_used_bits.l_cob_variance = 11; + cmp_set_max_used_bits(&max_used_bits); + + cfg = cmp_cfg_icu_create(DATA_TYPE_L_FX_NCOB, CMP_MODE_DIFF_ZERO, 0, CMP_LOSSLESS); + TEST_ASSERT(cfg.data_type != DATA_TYPE_UNKNOWN); + + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + CMP_PAR_UNUSED, CMP_PAR_UNUSED, CMP_PAR_UNUSED, CMP_PAR_UNUSED, + cmp_par_fx_cob_variance, spillover_fx_cob_variance); + TEST_ASSERT_FALSE(error); + + compressed_data_size = cmp_cfg_icu_buffers(&cfg, data_to_compress, 3, NULL, NULL, + (uint32_t *)compressed_data, 1); + TEST_ASSERT_EQUAL_INT(sizeof(compressed_data), compressed_data_size); + + /* value is to big for the max used bits values */ + data_p[2].exp_flags = 0x800000; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[2].exp_flags = 0x800000-1; + data_p[2].fx = 0x80000000; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[2].fx = 0x80000000-1; + data_p[2].ncob_x = 0x4; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[2].ncob_x = 0x3; + data_p[2].ncob_y = 0x4; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[2].ncob_y = 0x3; + data_p[0].fx_variance = 0x800000; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[0].fx_variance = 0x800000-1; + data_p[2].cob_x_variance = 0x800; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[2].cob_x_variance = 0x800-1; + data_p[2].cob_y_variance = 0x800; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[2].cob_y_variance = 0x800-1; + + max_used_bits.l_exp_flags = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.l_exp_flags = 32; + max_used_bits.l_fx = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.l_fx = 32; + max_used_bits.l_ncob = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.l_ncob = 32; + max_used_bits.l_fx_variance = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.l_fx_variance = 32; + max_used_bits.l_cob_variance = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); +} + + +/** + * @test compress_l_fx_efx_ncob_ecob + */ + +void test_compress_l_fx_efx_ncob_ecob_error_cases(void) +{ + int error, cmp_bits, compressed_data_size; + struct cmp_cfg cfg = {0}; + uint32_t cmp_par_exp_flags = MAX_ICU_GOLOMB_PAR; + uint32_t spillover_exp_flags = cmp_icu_max_spill(cmp_par_exp_flags); + uint32_t cmp_par_fx = 1; + uint32_t spillover_fx = 8; + uint32_t cmp_par_ncob = 2; + uint32_t spillover_ncob = 10; + uint32_t cmp_par_efx = 3; + uint32_t spillover_efx = 44; + uint32_t cmp_par_ecob = 5; + uint32_t spillover_ecob = 55; + uint32_t cmp_par_fx_cob_variance = 30; + uint32_t spillover_fx_cob_variance = 8; + uint8_t data_to_compress[MULTI_ENTRY_HDR_SIZE+3*sizeof(struct l_fx_efx_ncob_ecob)] = {0}; + uint8_t compressed_data[MULTI_ENTRY_HDR_SIZE+1*sizeof(struct l_fx_efx_ncob_ecob)] = {0}; + struct cmp_max_used_bits max_used_bits = {0}; + struct l_fx_efx_ncob_ecob *data_p = (struct l_fx_efx_ncob_ecob *)&data_to_compress[MULTI_ENTRY_HDR_SIZE]; + + max_used_bits.l_exp_flags = 23; + max_used_bits.l_fx = 31; + max_used_bits.l_ncob = 2; + max_used_bits.l_efx = 1; + max_used_bits.l_ecob = 3; + max_used_bits.l_fx_variance = 23; + max_used_bits.l_cob_variance = 11; + cmp_set_max_used_bits(&max_used_bits); + + cfg = cmp_cfg_icu_create(DATA_TYPE_L_FX_EFX_NCOB_ECOB, CMP_MODE_DIFF_ZERO, 0, CMP_LOSSLESS); + TEST_ASSERT(cfg.data_type != DATA_TYPE_UNKNOWN); + + error = cmp_cfg_fx_cob(&cfg, cmp_par_exp_flags, spillover_exp_flags, + cmp_par_fx, spillover_fx, cmp_par_ncob, spillover_ncob, + cmp_par_efx, spillover_efx, cmp_par_ecob, spillover_ecob, + cmp_par_fx_cob_variance, spillover_fx_cob_variance); + TEST_ASSERT_FALSE(error); + + compressed_data_size = cmp_cfg_icu_buffers(&cfg, data_to_compress, 3, NULL, NULL, + (uint32_t *)compressed_data, 1); + TEST_ASSERT_EQUAL_INT(sizeof(compressed_data), compressed_data_size); + + /* value is to big for the max used bits values */ + data_p[2].exp_flags = 0x800000; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[2].exp_flags = 0x800000-1; + data_p[2].fx = 0x80000000; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[2].fx = 0x80000000-1; + data_p[2].ncob_x = 0x4; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[2].ncob_x = 0x3; + data_p[2].ncob_y = 0x4; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[2].ncob_y = 0x3; + data_p[1].efx = 0x2; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[1].efx = 0x1; + data_p[1].ecob_x = 0x8; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[1].ecob_x = 0x7; + data_p[1].ecob_y = 0x8; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[1].ecob_y = 0x7; + data_p[0].fx_variance = 0x800000; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[0].fx_variance = 0x800000-1; + data_p[2].cob_x_variance = 0x800; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[2].cob_x_variance = 0x800-1; + data_p[2].cob_y_variance = 0x800; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[2].cob_y_variance = 0x800-1; + + max_used_bits.l_exp_flags = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.l_exp_flags = 32; + max_used_bits.l_fx = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.l_fx = 32; + max_used_bits.l_ncob = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.l_ncob = 32; + max_used_bits.l_efx = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.l_efx = 32; + max_used_bits.l_ecob = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.l_ecob = 32; + max_used_bits.l_fx_variance = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.l_fx_variance = 32; + max_used_bits.l_cob_variance = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); +} + + +/** + * @test compress_nc_offset + */ + +void test_compress_nc_offset_error_cases(void) +{ + int error, cmp_bits, compressed_data_size; + struct cmp_cfg cfg = {0}; + uint32_t cmp_par_mean = 1; + uint32_t spillover_mean = 2; + uint32_t cmp_par_variance = MAX_ICU_GOLOMB_PAR; + uint32_t spillover_variance = cmp_icu_max_spill(MAX_ICU_GOLOMB_PAR); + uint8_t data_to_compress[MULTI_ENTRY_HDR_SIZE+3*sizeof(struct nc_offset)] = {0}; + uint8_t compressed_data[MULTI_ENTRY_HDR_SIZE+1*sizeof(struct nc_offset)] = {0}; + struct cmp_max_used_bits max_used_bits = {0}; + struct nc_offset *data_p = (struct nc_offset *)&data_to_compress[MULTI_ENTRY_HDR_SIZE]; + + max_used_bits.nc_offset_mean = 1; + max_used_bits.nc_offset_variance = 31; + cmp_set_max_used_bits(&max_used_bits); + + cfg = cmp_cfg_icu_create(DATA_TYPE_OFFSET, CMP_MODE_DIFF_MULTI, 0, CMP_LOSSLESS); + TEST_ASSERT(cfg.data_type != DATA_TYPE_UNKNOWN); + + error = cmp_cfg_aux(&cfg, cmp_par_mean, spillover_mean, + cmp_par_variance, spillover_variance, + CMP_PAR_UNUSED, CMP_PAR_UNUSED); + TEST_ASSERT_FALSE(error); + + compressed_data_size = cmp_cfg_icu_buffers(&cfg, data_to_compress, 3, NULL, NULL, + (uint32_t *)compressed_data, 1); + TEST_ASSERT_EQUAL_INT(sizeof(compressed_data), compressed_data_size); + + /* value is to big for the max used bits values */ + data_p[0].mean = 0x2; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[0].mean = 0x1; + data_p[1].variance = 0x80000000; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[1].variance = 0x80000000-1; + + max_used_bits.nc_offset_mean = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.nc_offset_mean = 32; + max_used_bits.nc_offset_variance = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); +} + + +/** + * @test compress_nc_background + */ + +void test_compress_nc_background_error_cases(void) +{ + int error, cmp_bits, compressed_data_size; + struct cmp_cfg cfg = {0}; + uint32_t cmp_par_mean = 1; + uint32_t spillover_mean = 2; + uint32_t cmp_par_variance = MAX_ICU_GOLOMB_PAR; + uint32_t spillover_variance = cmp_icu_max_spill(MAX_ICU_GOLOMB_PAR); + uint32_t cmp_par_pixels_error = 23; + uint32_t spillover_pixels_error = 42; + uint8_t data_to_compress[MULTI_ENTRY_HDR_SIZE+3*sizeof(struct nc_background)] = {0}; + uint8_t compressed_data[MULTI_ENTRY_HDR_SIZE+1*sizeof(struct nc_background)] = {0}; + struct cmp_max_used_bits max_used_bits = {0}; + struct nc_background *data_p = (struct nc_background *)&data_to_compress[MULTI_ENTRY_HDR_SIZE]; + + max_used_bits.nc_background_mean = 1; + max_used_bits.nc_background_variance = 31; + max_used_bits.nc_background_outlier_pixels = 2; + cmp_set_max_used_bits(&max_used_bits); + + cfg = cmp_cfg_icu_create(DATA_TYPE_BACKGROUND, CMP_MODE_DIFF_MULTI, 0, CMP_LOSSLESS); + TEST_ASSERT(cfg.data_type != DATA_TYPE_UNKNOWN); + + error = cmp_cfg_aux(&cfg, cmp_par_mean, spillover_mean, + cmp_par_variance, spillover_variance, + cmp_par_pixels_error, spillover_pixels_error); + TEST_ASSERT_FALSE(error); + + compressed_data_size = cmp_cfg_icu_buffers(&cfg, data_to_compress, 3, NULL, NULL, + (uint32_t *)compressed_data, 1); + TEST_ASSERT_EQUAL_INT(sizeof(compressed_data), compressed_data_size); + + /* value is to big for the max used bits values */ + data_p[0].mean = 0x2; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[0].mean = 0x1; + data_p[1].variance = 0x80000000; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[1].variance = 0x80000000-1; + data_p[1].outlier_pixels = 0x4; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[1].outlier_pixels = 0x3; + + max_used_bits.nc_background_mean = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.nc_background_mean = 32; + max_used_bits.nc_background_variance = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.nc_background_variance = 32; + max_used_bits.nc_background_outlier_pixels = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); +} + + +/** + * @test compress_smearing + */ + +void test_compress_smearing_error_cases(void) +{ + int error, cmp_bits, compressed_data_size; + struct cmp_cfg cfg = {0}; + uint32_t cmp_par_mean = 1; + uint32_t spillover_mean = 2; + uint32_t cmp_par_variance = MAX_ICU_GOLOMB_PAR; + uint32_t spillover_variance = cmp_icu_max_spill(MAX_ICU_GOLOMB_PAR); + uint32_t cmp_par_pixels_error = 23; + uint32_t spillover_pixels_error = 42; + uint8_t data_to_compress[MULTI_ENTRY_HDR_SIZE+3*sizeof(struct smearing)] = {0}; + uint8_t compressed_data[MULTI_ENTRY_HDR_SIZE+1*sizeof(struct smearing)] = {0}; + struct cmp_max_used_bits max_used_bits = {0}; + struct smearing *data_p = (struct smearing *)&data_to_compress[MULTI_ENTRY_HDR_SIZE]; + + max_used_bits.smearing_mean = 1; + max_used_bits.smearing_variance_mean = 15; + max_used_bits.smearing_outlier_pixels = 2; + cmp_set_max_used_bits(&max_used_bits); + + cfg = cmp_cfg_icu_create(DATA_TYPE_SMEARING, CMP_MODE_DIFF_MULTI, 0, CMP_LOSSLESS); + TEST_ASSERT(cfg.data_type != DATA_TYPE_UNKNOWN); + + error = cmp_cfg_aux(&cfg, cmp_par_mean, spillover_mean, + cmp_par_variance, spillover_variance, + cmp_par_pixels_error, spillover_pixels_error); + TEST_ASSERT_FALSE(error); + + compressed_data_size = cmp_cfg_icu_buffers(&cfg, data_to_compress, 3, NULL, NULL, + (uint32_t *)compressed_data, 1); + TEST_ASSERT_EQUAL_INT(sizeof(compressed_data), compressed_data_size); + + /* value is to big for the max used bits values */ + data_p[0].mean = 0x2; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[0].mean = 0x1; + data_p[1].variance_mean = 0x8000; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[1].variance_mean = 0x8000-1; + data_p[1].outlier_pixels = 0x4; + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_HIGH_VALUE, cmp_bits); + + data_p[1].outlier_pixels = 0x3; + + max_used_bits.smearing_mean = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.smearing_mean = 32; + max_used_bits.smearing_variance_mean = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); + + max_used_bits.smearing_variance_mean = 32; + max_used_bits.smearing_outlier_pixels = 33; /* more than 32 bits are not allowed */ + cmp_set_max_used_bits(&max_used_bits); + cmp_bits = icu_compress_data(&cfg); + TEST_ASSERT_EQUAL_INT(-1, cmp_bits); +} + + +/** + * @test pad_bitstream + */ + +void test_pad_bitstream(void) +{ + struct cmp_cfg cfg = {0}; + int cmp_size; + int cmp_size_return; + uint32_t cmp_data[3]; + const int MAX_BIT_LEN = 96; + + memset(cmp_data, 0xFF, sizeof(cmp_data)); + cfg.icu_output_buf = cmp_data; + cfg.data_type = DATA_TYPE_IMAGETTE; /* 16 bit samples */ + cfg.buffer_length = 6; /* 6 * 16 bit samples -> 3 * 32 bit */ + + /* test negative cmp_size */ + cmp_size = -1; + cmp_size_return = pad_bitstream(&cfg, cmp_size); + TEST_ASSERT_EQUAL_INT(-1, cmp_size_return); + cmp_size = -3; + cmp_size_return = pad_bitstream(&cfg, cmp_size); + TEST_ASSERT_EQUAL_INT(-3, cmp_size_return); + + /* test RAW_MODE */ + cfg.cmp_mode = CMP_MODE_RAW; + cmp_size = MAX_BIT_LEN; + cmp_size_return = pad_bitstream(&cfg, cmp_size); + TEST_ASSERT_EQUAL_INT(MAX_BIT_LEN, cmp_size_return); + TEST_ASSERT_EQUAL_INT(cmp_data[0], 0xFFFFFFFF); + TEST_ASSERT_EQUAL_INT(cmp_data[1], 0xFFFFFFFF); + TEST_ASSERT_EQUAL_INT(cmp_data[2], 0xFFFFFFFF); + + /* test Normal operation */ + cfg.cmp_mode = CMP_MODE_MODEL_MULTI; + cmp_size = 0; + /* set the first 32 bits zero no change should occur */ + cmp_size = put_n_bits32(0, 32, cmp_size, cfg.icu_output_buf, MAX_BIT_LEN); + cmp_size_return = pad_bitstream(&cfg, cmp_size); + TEST_ASSERT_EQUAL_INT(cmp_size, cmp_size_return); + TEST_ASSERT_EQUAL_INT(cmp_data[0], 0); + TEST_ASSERT_EQUAL_INT(cmp_data[1], 0xFFFFFFFF); + TEST_ASSERT_EQUAL_INT(cmp_data[2], 0xFFFFFFFF); + + /* set the first 33 bits zero; and checks the padding */ + cmp_size = put_n_bits32(0, 1, cmp_size, cfg.icu_output_buf, MAX_BIT_LEN); + cmp_size_return = pad_bitstream(&cfg, cmp_size); + TEST_ASSERT_EQUAL_INT(cmp_size, cmp_size_return); + TEST_ASSERT_EQUAL_INT(cmp_data[0], 0); + TEST_ASSERT_EQUAL_INT(cmp_data[1], 0); + TEST_ASSERT_EQUAL_INT(cmp_data[2], 0xFFFFFFFF); + + /* set the first 63 bits zero; and checks the padding */ + cmp_data[1] = 0xFFFFFFFF; + cmp_size = 32; + cmp_size = put_n_bits32(0, 31, cmp_size, cfg.icu_output_buf, MAX_BIT_LEN); + cmp_size_return = pad_bitstream(&cfg, cmp_size); + TEST_ASSERT_EQUAL_INT(cmp_size, cmp_size_return); + TEST_ASSERT_EQUAL_INT(cmp_data[0], 0); + TEST_ASSERT_EQUAL_INT(cmp_data[1], 0); + TEST_ASSERT_EQUAL_INT(cmp_data[2], 0xFFFFFFFF); + + /* error case the rest of the compressed data are to small dor a 32 bit + * access */ + cfg.buffer_length = 5; + cmp_size = 64; + cmp_size = put_n_bits32(0, 1, cmp_size, cfg.icu_output_buf, MAX_BIT_LEN); + cmp_size_return = pad_bitstream(&cfg, cmp_size); + TEST_ASSERT_EQUAL_INT(CMP_ERROR_SMALL_BUF, cmp_size_return); +} + + +/** + * @test cmp_data_to_big_endian + */ + +void test_cmp_data_to_big_endian_error_cases(void) +{ + struct cmp_cfg cfg = {0}; + int cmp_size; + int cmp_size_return; + uint16_t cmp_data[3] = {0x0123, 0x4567, 0x89AB}; + uint8_t *p; + + cfg.icu_output_buf = (uint32_t *)cmp_data; + + /* this should work */ + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.cmp_mode = CMP_MODE_RAW; + cmp_size = 48; + cmp_size_return = cmp_data_to_big_endian(&cfg, cmp_size); + TEST_ASSERT_EQUAL_INT(cmp_size_return, 48); + p = (uint8_t *)cfg.icu_output_buf; + TEST_ASSERT_EQUAL(p[0], 0x01); + TEST_ASSERT_EQUAL(p[1], 0x23); + TEST_ASSERT_EQUAL(p[2], 0x45); + TEST_ASSERT_EQUAL(p[3], 0x67); + TEST_ASSERT_EQUAL(p[4], 0x89); + TEST_ASSERT_EQUAL(p[5], 0xAB); + + /* error cases */ + cmp_data[0] = 0x0123; + cmp_data[1] = 0x4567; + cmp_data[2] = 0x89AB; + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.cmp_mode = CMP_MODE_RAW; + cmp_size = 47; /* wrong size */ + cmp_size_return = cmp_data_to_big_endian(&cfg, cmp_size); + TEST_ASSERT_EQUAL_INT(cmp_size_return, -1); + + cmp_data[0] = 0x0123; + cmp_data[1] = 0x4567; + cmp_data[2] = 0x89AB; + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.cmp_mode = CMP_MODE_RAW; + cmp_size = 49; /* wrong size */ + cmp_size_return = cmp_data_to_big_endian(&cfg, cmp_size); + TEST_ASSERT_EQUAL_INT(cmp_size_return, -1); + + cmp_data[0] = 0x0123; + cmp_data[1] = 0x4567; + cmp_data[2] = 0x89AB; + cfg.data_type = DATA_TYPE_UNKNOWN; /* wrong data_type */ + cfg.cmp_mode = CMP_MODE_RAW; + cmp_size = 48; + cmp_size_return = cmp_data_to_big_endian(&cfg, cmp_size); + TEST_ASSERT_EQUAL_INT(cmp_size_return, -1); +} /** @@ -3024,8 +4364,6 @@ void test_icu_compress_data_error_cases(void) { int cmp_size; struct cmp_cfg cfg = {0}; - uint16_t data[4] = {0, 0xFFFF, 23, 42}; - uint32_t cmp_data_buf[4] = {0}; /* cfg = NULL test */ cmp_size = icu_compress_data(NULL); @@ -3035,6 +4373,5 @@ void test_icu_compress_data_error_cases(void) cfg.samples = 0; cmp_size = icu_compress_data(&cfg); TEST_ASSERT_EQUAL(0, cmp_size); - } -- GitLab From d63effd98c023bc9772ccf6665c3940e0695e3fb Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Tue, 20 Sep 2022 12:30:12 +0200 Subject: [PATCH 31/46] Add check in the cmp_cfg_icu_buffers function if the compressed data can fit in a compression entity fix some typos; disable 3 times smaller warning if cfg->icu_output_buf = NULL --- include/cmp_icu.h | 8 ++++---- lib/cmp_icu.c | 26 +++++++++++++++++++------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/include/cmp_icu.h b/include/cmp_icu.h index 8b68fcb..743e7d1 100644 --- a/include/cmp_icu.h +++ b/include/cmp_icu.h @@ -28,10 +28,10 @@ struct cmp_cfg cmp_cfg_icu_create(enum cmp_data_type data_type, enum cmp_mode cmp_mode, uint32_t model_value, uint32_t lossy_par); -size_t cmp_cfg_icu_buffers(struct cmp_cfg *cfg, void *data_to_compress, - uint32_t data_samples, void *model_of_data, - void *updated_model, uint32_t *compressed_data, - uint32_t compressed_data_len_samples); +uint32_t cmp_cfg_icu_buffers(struct cmp_cfg *cfg, void *data_to_compress, + uint32_t data_samples, void *model_of_data, + void *updated_model, uint32_t *compressed_data, + uint32_t compressed_data_len_samples); int cmp_cfg_icu_imagette(struct cmp_cfg *cfg, uint32_t cmp_par, uint32_t spillover_par); diff --git a/lib/cmp_icu.c b/lib/cmp_icu.c index 107b85d..a05a98f 100644 --- a/lib/cmp_icu.c +++ b/lib/cmp_icu.c @@ -37,6 +37,7 @@ #include <cmp_data_types.h> #include <cmp_support.h> #include <cmp_icu.h> +#include <cmp_entity.h> /* maximum used bits registry */ @@ -118,11 +119,13 @@ struct cmp_cfg cmp_cfg_icu_create(enum cmp_data_type data_type, enum cmp_mode cm * parameters are invalid */ -size_t cmp_cfg_icu_buffers(struct cmp_cfg *cfg, void *data_to_compress, - uint32_t data_samples, void *model_of_data, - void *updated_model, uint32_t *compressed_data, - uint32_t compressed_data_len_samples) +uint32_t cmp_cfg_icu_buffers(struct cmp_cfg *cfg, void *data_to_compress, + uint32_t data_samples, void *model_of_data, + void *updated_model, uint32_t *compressed_data, + uint32_t compressed_data_len_samples) { + uint32_t data_size, hdr_size; + if (!cfg) { debug_print("Error: pointer to the compression configuration structure is NULL.\n"); return 0; @@ -138,7 +141,15 @@ size_t cmp_cfg_icu_buffers(struct cmp_cfg *cfg, void *data_to_compress, if (!cmp_cfg_icu_buffers_is_valid(cfg)) return 0; - return cmp_cal_size_of_data(compressed_data_len_samples, cfg->data_type); + data_size = cmp_cal_size_of_data(compressed_data_len_samples, cfg->data_type); + hdr_size = cmp_ent_cal_hdr_size(cfg->data_type, cfg->cmp_mode == CMP_MODE_RAW); + + if ((data_size + hdr_size) > CMP_ENTITY_MAX_SIZE) { + debug_print("Error: The buffer for the compressed data is too large to fit in a compression entity.\n"); + return 0; + } + + return data_size; } @@ -643,7 +654,7 @@ static int encode_value_multi(uint32_t data, uint32_t model, int stream_len, /** - * @brief put the value unencoded with(setup->cmp_par_1 bits without any changes + * @brief put the value unencoded with setup->cmp_par_1 bits without any changes * in the bitstream * * @param value value to put unchanged in the bitstream @@ -783,6 +794,7 @@ static int configure_encoder_setup(struct encoder_setupt *setup, setup->encode_method_f = &encode_value_none; setup->max_data_bits = cmp_par; break; + case CMP_MODE_RAW: default: return -1; } @@ -2340,7 +2352,7 @@ int icu_compress_data(const struct cmp_cfg *cfg) memcpy(cfg->icu_output_buf, cfg->input_buf, cmp_size); cmp_size *= CHAR_BIT; /* convert to bits */ } else { - if (cfg->samples < cfg->buffer_length/3) + if (cfg->icu_output_buf && cfg->samples/3 > cfg->buffer_length) debug_print("Warning: The size of the compressed_data buffer is 3 times smaller than the data_to_compress. This is probably unintended.\n"); switch (cfg->data_type) { -- GitLab From 315ce82bbece6bd37bb9dc6167a90eea614febdf Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Tue, 20 Sep 2022 13:42:39 +0200 Subject: [PATCH 32/46] fix some type stuff --- lib/cmp_data_types.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/cmp_data_types.c b/lib/cmp_data_types.c index 3064e6c..ee0131f 100644 --- a/lib/cmp_data_types.c +++ b/lib/cmp_data_types.c @@ -17,11 +17,14 @@ */ +#include <stdint.h> +#include <stdio.h> +#include <limits.h> + + #include <cmp_data_types.h> #include <cmp_debug.h> #include <byteorder.h> -#include <stdint.h> -#include <stdio.h> /* the maximum length of the different data products types in bits */ @@ -187,12 +190,12 @@ size_t size_of_a_sample(enum cmp_data_type data_type) * * @note for non-imagette data program types the multi entry header size is added * - * @returns the size in bytes to store the data sample + * @returns the size in bytes to store the data sample; zero on failure */ unsigned int cmp_cal_size_of_data(unsigned int samples, enum cmp_data_type data_type) { - unsigned int s = size_of_a_sample(data_type); + size_t s = size_of_a_sample(data_type); uint64_t x; /* use 64 bit to catch overflow */ if (!s) @@ -203,7 +206,7 @@ unsigned int cmp_cal_size_of_data(unsigned int samples, enum cmp_data_type data_ if (!rdcu_supported_data_type_is_used(data_type)) x += MULTI_ENTRY_HDR_SIZE; - if (x > UINT32_MAX) /* catch overflow */ + if (x > UINT_MAX) /* catch overflow */ return 0; return (unsigned int)x; @@ -222,7 +225,7 @@ unsigned int cmp_cal_size_of_data(unsigned int samples, enum cmp_data_type data_ int cmp_input_size_to_samples(unsigned int size, enum cmp_data_type data_type) { - int samples_size = (int)size_of_a_sample(data_type); + uint32_t samples_size = size_of_a_sample(data_type); if (!samples_size) return -1; @@ -236,7 +239,7 @@ int cmp_input_size_to_samples(unsigned int size, enum cmp_data_type data_type) if (size % samples_size) return -1; - return size/samples_size; + return (int)(size/samples_size); } -- GitLab From acf80c8c013f4a179bad28036dca21ede07f0327 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Tue, 20 Sep 2022 13:47:13 +0200 Subject: [PATCH 33/46] minor changes --- include/cmp_support.h | 2 +- lib/cmp_support.c | 18 ++++-------------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/include/cmp_support.h b/include/cmp_support.h index b69eb5c..d0ffbe3 100644 --- a/include/cmp_support.h +++ b/include/cmp_support.h @@ -153,7 +153,7 @@ struct cmp_cfg { uint32_t rdcu_new_model_adr;/* RDCU updated model start address, the address in the RDCU SRAM where the updated model is stored */ uint32_t rdcu_buffer_adr; /* RDCU compressed data start address, the first output data address in the RDCU SRAM */ enum cmp_data_type data_type; /* Compression Data Product Types */ - uint32_t cmp_mode; /* 0: raw mode + enum cmp_mode cmp_mode; /* 0: raw mode * 1: model mode with zero escape symbol mechanism * 2: 1d differencing mode without input model with zero escape symbol mechanism * 3: model mode with multi escape symbol mechanism diff --git a/lib/cmp_support.c b/lib/cmp_support.c index f157a6e..5572a90 100644 --- a/lib/cmp_support.c +++ b/lib/cmp_support.c @@ -149,6 +149,7 @@ int rdcu_supported_cmp_mode_is_used(enum cmp_mode cmp_mode) case CMP_MODE_MODEL_MULTI: case CMP_MODE_DIFF_MULTI: return 1; + case CMP_MODE_STUFF: default: return 0; } @@ -250,17 +251,7 @@ int multi_escape_mech_is_used(enum cmp_mode cmp_mode) int cmp_imagette_data_type_is_used(enum cmp_data_type data_type) { - switch (data_type) { - case DATA_TYPE_IMAGETTE: - case DATA_TYPE_IMAGETTE_ADAPTIVE: - case DATA_TYPE_SAT_IMAGETTE: - case DATA_TYPE_SAT_IMAGETTE_ADAPTIVE: - case DATA_TYPE_F_CAM_IMAGETTE: - case DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE: - return 1; - default: - return 0; - } + return rdcu_supported_data_type_is_used(data_type); } @@ -487,7 +478,7 @@ int cmp_cfg_icu_gen_par_is_valid(const struct cmp_cfg *cfg) } if (cfg->cmp_mode > CMP_MODE_STUFF) { - debug_print("Error: selected cmp_mode: %u is not supported.\n", cfg->cmp_mode); + debug_print("Error: selected cmp_mode: %i is not supported.\n", cfg->cmp_mode); cfg_invalid++; } @@ -594,7 +585,6 @@ int cmp_cfg_icu_buffers_is_valid(const struct cmp_cfg *cfg) * @param cmp_par compression parameter * @param spill spillover threshold parameter * @param cmp_mode compression mode - * @param data_type compression data type * @param par_name string describing the use of the compression par. for * debug messages (can be NULL) * @@ -891,7 +881,7 @@ void print_cmp_cfg(const struct cmp_cfg *cfg) { size_t i; - printf("cmp_mode: %u\n", cfg->cmp_mode); + printf("cmp_mode: %i\n", cfg->cmp_mode); printf("golomb_par: %u\n", cfg->golomb_par); printf("spill: %u\n", cfg->spill); printf("model_value: %u\n", cfg->model_value); -- GitLab From 11f472d00b8c3ca25455a2134b630f199998bec8 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Tue, 20 Sep 2022 13:49:06 +0200 Subject: [PATCH 34/46] fix a bug in the cmp_ent_get_data_buf() function --- lib/cmp_entity.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/cmp_entity.c b/lib/cmp_entity.c index c0b2898..b66216c 100644 --- a/lib/cmp_entity.c +++ b/lib/cmp_entity.c @@ -1650,13 +1650,13 @@ void *cmp_ent_get_data_buf(struct cmp_entity *ent) switch (data_type) { case DATA_TYPE_IMAGETTE: + case DATA_TYPE_SAT_IMAGETTE: case DATA_TYPE_F_CAM_IMAGETTE: return ent->ima.ima_cmp_dat; case DATA_TYPE_IMAGETTE_ADAPTIVE: + case DATA_TYPE_SAT_IMAGETTE_ADAPTIVE: case DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE: return ent->ima.ap_ima_cmp_data; - case DATA_TYPE_SAT_IMAGETTE: - case DATA_TYPE_SAT_IMAGETTE_ADAPTIVE: case DATA_TYPE_OFFSET: case DATA_TYPE_BACKGROUND: case DATA_TYPE_SMEARING: @@ -2038,7 +2038,7 @@ int cmp_ent_write_rdcu_cmp_pars(struct cmp_entity *ent, const struct cmp_info *i * returns the needed size * @param data_type compression entity data product type * @param raw_mode_flag set this flag if the raw compression mode (CMP_MODE_RAW) is used - * @param cmp_size_byte size of the compressed data in bytes + * @param cmp_size_byte size of the compressed data in bytes (should be a multiple of 4) * * @note if the entity size is smaller than the largest header, the function * rounds up the entity size to the largest header @@ -2063,6 +2063,8 @@ uint32_t cmp_ent_create(struct cmp_entity *ent, enum cmp_data_type data_type, if (ent_size > CMP_ENTITY_MAX_SIZE) return 0; + /* to be safe a compression entity should be at least the size of the + * largest entity header */ if (ent_size < sizeof(struct cmp_entity)) ent_size = sizeof(struct cmp_entity); -- GitLab From c6cf8e0c004324d46fc6113dddd0e39ca5dd9447 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Tue, 20 Sep 2022 14:27:19 +0200 Subject: [PATCH 35/46] add STUFF mode decompression remove setup->max_stream_len assume now that the max cw_len is always 32 bits add a input parameter check for the rice_decoder() function change input parameter check for get_n_bits32() function --- lib/decmp.c | 80 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 22 deletions(-) diff --git a/lib/decmp.c b/lib/decmp.c index f15c943..da75d50 100644 --- a/lib/decmp.c +++ b/lib/decmp.c @@ -32,6 +32,8 @@ #include "cmp_entity.h" +#define MAX_CW_LEN 32 /* maximum Golomb code word bit length */ + /* maximum used bits registry */ extern struct cmp_max_used_bits max_used_bits; @@ -45,7 +47,6 @@ struct decoder_setup { const struct decoder_setup *setup); /* pointer to the decoding function */ uint32_t *bitstream_adr; /* start address of the compressed data bitstream */ uint32_t max_stream_len; /* maximum length of the bitstream/icu_output_buf in bits */ - uint32_t max_cw_len; /* TODO */ uint32_t encoder_par1; /* encoding parameter 1 */ uint32_t encoder_par2; /* encoding parameter 2 */ uint32_t outlier_par; /* outlier parameter */ @@ -93,8 +94,8 @@ static unsigned int count_leading_ones(uint32_t value) * 0 on failure */ -static int rice_decoder(uint32_t code_word, unsigned int m, - unsigned int log2_m, unsigned int *decoded_cw) +static int rice_decoder(uint32_t code_word, unsigned int m, unsigned int log2_m, + unsigned int *decoded_cw) { unsigned int q; /* quotient code */ unsigned int ql; /* length of the quotient code */ @@ -104,6 +105,9 @@ static int rice_decoder(uint32_t code_word, unsigned int m, (void)m; /* we don't need the Golomb parameter */ + if (log2_m > 32) + return 0; + q = count_leading_ones(code_word); /* decode unary coding */ ql = q + 1; /* Number of 1's + following 0 */ @@ -124,7 +128,7 @@ static int rice_decoder(uint32_t code_word, unsigned int m, else r = code_word >> (32 - rl); - *decoded_cw = (q << log2_m) + r; + *decoded_cw = (q << rl) + r; return cw_len; } @@ -206,16 +210,15 @@ static decoder_ptr select_decoder(unsigned int golomb_par) /** * @brief read a value of up to 32 bits from a bitstream * - * @param p_value pointer to the read value (can be NULL), the + * @param p_value pointer to the read value, the * read value will be converted to the system * endianness * @param n_bits number of bits to read from the bitstream * @param bit_offset bit index where the bits will be read, seen from * the very beginning of the bitstream * @param bitstream_adr this is the pointer to the beginning of the - * bitstream (can be NULL) - * @param max_stream_len maximum length of the bitstream in bits; is - * ignored if bitstream_adr is NULL + * bitstream + * @param max_stream_len maximum length of the bitstream in bits * * * @returns bit position of the last read bit in the bitstream on success; * returns negative in case of erroneous input; returns CMP_ERROR_SMALL_BUF @@ -238,12 +241,10 @@ static int get_n_bits32(uint32_t *p_value, unsigned int n_bits, int bit_offset, return -1; if (n_bits > 32) return -1; - - /* nothing to read */ if (!bitstream_adr) - return stream_len; + return -1; if (!p_value) - return stream_len; + return -1; /* Check if bitstream buffer is large enough */ if ((unsigned int)stream_len > max_stream_len) { @@ -310,10 +311,10 @@ static int decode_normal(uint32_t *decoded_value, int stream_pos, /* check if we can read max_cw_len or less; we do not now how long the * code word actually is so we try to read the maximum cw length */ - if ((unsigned int)stream_pos + setup->max_cw_len > setup->max_stream_len) + if ((unsigned int)stream_pos + 32 > setup->max_stream_len) n_read_bits = setup->max_stream_len - (unsigned int)stream_pos; else - n_read_bits = setup->max_cw_len; + n_read_bits = MAX_CW_LEN; stream_pos_read = get_n_bits32(&read_val, n_read_bits, stream_pos, setup->bitstream_adr, setup->max_stream_len); @@ -410,13 +411,37 @@ static int decode_multi(uint32_t *decoded_value, int stream_pos, stream_pos = get_n_bits32(&unencoded_val, unencoded_len, stream_pos, setup->bitstream_adr, setup->max_stream_len); - - *decoded_value = unencoded_val + setup->outlier_par; + if (stream_pos >= 0) + *decoded_value = unencoded_val + setup->outlier_par; } return stream_pos; } +/** + * @brief get the value unencoded with setup->cmp_par_1 bits without any + * additional changes from the bitstream + * + * @param decoded_value pointer to the decoded value + * @param stream_pos start bit position code word to be decoded in the bitstream + * @param setup pointer to the decoder setup + * + * @returns bit index of the next code word in the bitstream on success; returns + * negative in case of erroneous input; returns CMP_ERROR_SMALL_BUF if the + * bitstream buffer is too small to read the value from the bitstream + * + */ + +static int decode_none(uint32_t *decoded_value, int stream_pos, + const struct decoder_setup *setup) +{ + stream_pos = get_n_bits32(decoded_value, setup->encoder_par1, stream_pos, + setup->bitstream_adr, setup->max_stream_len); + + return stream_pos; +} + + /** * @brief remap a unsigned value back to a signed value * @note this is the reverse function of map_to_pos() @@ -461,11 +486,14 @@ static int decode_value(uint32_t *decoded_value, uint32_t model, if (stream_pos <= 0) return stream_pos; + if (setup->decode_method_f == decode_none) /* we are done here in stuff mode */ + return stream_pos; + *decoded_value = re_map_to_pos(*decoded_value); *decoded_value += round_fwd(model, setup->lossy_par); - *decoded_value &= mask; + *decoded_value &= mask; /* TODO: why?? */ *decoded_value = round_inv(*decoded_value, setup->lossy_par); @@ -482,7 +510,10 @@ static int configure_decoder_setup(struct decoder_setup *setup, setup->decode_method_f = &decode_multi; else if (zero_escape_mech_is_used(cfg->cmp_mode)) setup->decode_method_f = &decode_zero; + else if (cfg->cmp_mode == CMP_MODE_STUFF) + setup->decode_method_f = &decode_none; else { + setup->decode_method_f = NULL; debug_print("Error: Compression mode not supported.\n"); return -1; } @@ -493,11 +524,9 @@ static int configure_decoder_setup(struct decoder_setup *setup, return -1; } setup->max_stream_len = (cfg->buffer_length) * CHAR_BIT; /* maximum length of the bitstream/icu_output_buf in bits */ - if (rdcu_supported_data_type_is_used(cfg->data_type)) - setup->max_cw_len = 16; - else - setup->max_cw_len = 32; setup->encoder_par1 = cmp_par; /* encoding parameter 1 */ + if (ilog_2(cmp_par) < 0) + return -1; setup->encoder_par2 = ilog_2(cmp_par); /* encoding parameter 2 */ setup->outlier_par = spill; /* outlier parameter */ setup->lossy_par = lossy_par; /* lossy compression parameter */ @@ -1708,6 +1737,12 @@ static int decompress_l_fx_efx_ncob_ecob(const struct cmp_cfg *cfg) cfg->model_value, setup_ncob.lossy_par); up_model_buf[i].ncob_y = cmp_up_model(data_buf[i].ncob_y, model.ncob_y, cfg->model_value, setup_ncob.lossy_par); + up_model_buf[i].efx = cmp_up_model(data_buf[i].efx, model.efx, + cfg->model_value, setup_efx.lossy_par); + up_model_buf[i].ecob_x = cmp_up_model(data_buf[i].ecob_x, model.ecob_x, + cfg->model_value, setup_ecob.lossy_par); + up_model_buf[i].ecob_y = cmp_up_model(data_buf[i].ecob_y, model.ecob_y, + cfg->model_value, setup_ecob.lossy_par); up_model_buf[i].fx_variance = cmp_up_model(data_buf[i].fx_variance, model.fx_variance, cfg->model_value, setup_fx_var.lossy_par); up_model_buf[i].cob_x_variance = cmp_up_model(data_buf[i].cob_x_variance, model.cob_x_variance, @@ -1968,7 +2003,8 @@ static int decompressed_data_internal(struct cmp_cfg *cfg) int data_size, strem_len_bit = -1; if (!cfg) - return 0; /* or -1? */ + return -1; + if (!cfg->icu_output_buf) return -1; -- GitLab From a814557224662714df6395ab765a292d15738f1b Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Mon, 26 Sep 2022 11:54:31 +0200 Subject: [PATCH 36/46] restrict the maximum compression golomb parameter for imagette compression, in order that the compression parameter fit into the imagette compression entity header; refactor the compression check functions --- include/cmp_support.h | 24 ++-- lib/cmp_guess.c | 18 +-- lib/cmp_icu.c | 28 +++-- lib/cmp_rdcu.c | 46 +++---- lib/cmp_support.c | 245 ++++++++++++++++++------------------ test/cmp_icu/test_cmp_icu.c | 35 +++--- test/cmp_icu/test_decmp.c | 54 +++++--- 7 files changed, 237 insertions(+), 213 deletions(-) diff --git a/include/cmp_support.h b/include/cmp_support.h index d0ffbe3..be598aa 100644 --- a/include/cmp_support.h +++ b/include/cmp_support.h @@ -38,11 +38,11 @@ #define MAX_MODEL_VALUE \ 16U /* the maximal model values used in the update equation for the new model */ -/* valid compression parameter ranges for RDCU compression according to PLATO-UVIE-PL-UM-0001 */ +/* valid compression parameter ranges for RDCU/ICU imagette compression according to PLATO-UVIE-PL-UM-0001 */ #define MAX_RDCU_CMP_MODE 4U -#define MIN_RDCU_GOLOMB_PAR 1U -#define MAX_RDCU_GOLOMB_PAR 63U -#define MIN_RDCU_SPILL 2U +#define MIN_IMA_GOLOMB_PAR 1U +#define MAX_IMA_GOLOMB_PAR 63U +#define MIN_IMA_SPILL 2U #define MAX_RDCU_ROUND 2U /* for maximum spill value look at cmp_rdcu_max_spill function */ @@ -91,6 +91,8 @@ #define CMP_DEF_IMA_DIFF_RDCU_UP_MODEL_ADR 0x000000 /* not needed for 1d-differencing cmp_mode */ #define CMP_DEF_IMA_DIFF_RDCU_BUFFER_ADR 0x600000 +enum {ICU_CHECK, RDCU_CHECK}; /* option for the cmp_cfg_imagette_is_invalid() function */ + /* defined compression data product types */ enum cmp_data_type { @@ -237,13 +239,13 @@ int ilog_2(uint32_t x); unsigned int cmp_bit_to_4byte(unsigned int cmp_size_bit); -int cmp_cfg_is_valid(const struct cmp_cfg *cfg); -int cmp_cfg_icu_gen_par_is_valid(const struct cmp_cfg *cfg); -int cmp_cfg_icu_buffers_is_valid(const struct cmp_cfg *cfg); -int cmp_cfg_imagette_is_valid(const struct cmp_cfg *cfg); -int cmp_cfg_fx_cob_is_valid(const struct cmp_cfg *cfg); -int cmp_cfg_aux_is_valid(const struct cmp_cfg *cfg); -uint32_t cmp_rdcu_max_spill(unsigned int golomb_par); +int cmp_cfg_is_invalid(const struct cmp_cfg *cfg); +int cmp_cfg_icu_gen_par_is_invalid(const struct cmp_cfg *cfg); +int cmp_cfg_icu_buffers_is_invalid(const struct cmp_cfg *cfg); +int cmp_cfg_imagette_is_invalid(const struct cmp_cfg *cfg, int rdcu_check); +int cmp_cfg_fx_cob_is_invalid(const struct cmp_cfg *cfg); +int cmp_cfg_aux_is_invalid(const struct cmp_cfg *cfg); +uint32_t cmp_ima_max_spill(unsigned int golomb_par); uint32_t cmp_icu_max_spill(unsigned int cmp_par); int cmp_data_type_valid(enum cmp_data_type data_type); diff --git a/lib/cmp_guess.c b/lib/cmp_guess.c index 40e3ebe..87aec6c 100644 --- a/lib/cmp_guess.c +++ b/lib/cmp_guess.c @@ -83,7 +83,7 @@ uint16_t cmp_guess_model_value(int n_model_updates) uint32_t cmp_rdcu_get_good_spill(unsigned int golomb_par, enum cmp_mode cmp_mode) { - const uint32_t LUT_RDCU_MULIT[MAX_RDCU_GOLOMB_PAR+1] = {0, 8, 16, 23, + const uint32_t LUT_IMA_MULIT[MAX_IMA_GOLOMB_PAR+1] = {0, 8, 16, 23, 30, 36, 44, 51, 58, 64, 71, 77, 84, 90, 97, 108, 115, 121, 128, 135, 141, 148, 155, 161, 168, 175, 181, 188, 194, 201, 207, 214, 229, 236, 242, 250, 256, 263, 269, 276, 283, 290, 296, 303, 310, @@ -91,13 +91,13 @@ uint32_t cmp_rdcu_get_good_spill(unsigned int golomb_par, enum cmp_mode cmp_mode 405, 411, 418, 424, 431, 452 }; if (zero_escape_mech_is_used(cmp_mode)) - return cmp_rdcu_max_spill(golomb_par); + return cmp_ima_max_spill(golomb_par); if (cmp_mode == CMP_MODE_MODEL_MULTI) { - if (golomb_par > MAX_RDCU_GOLOMB_PAR) + if (golomb_par > MAX_IMA_GOLOMB_PAR) return 0; else - return LUT_RDCU_MULIT[golomb_par]; + return LUT_IMA_MULIT[golomb_par]; } if (cmp_mode == CMP_MODE_DIFF_MULTI) @@ -123,7 +123,7 @@ static uint32_t pre_cal_method(struct cmp_cfg *cfg) uint32_t golomb_par_best = 0; uint32_t spill_best = 0; - for (g = MIN_RDCU_GOLOMB_PAR; g < MAX_RDCU_GOLOMB_PAR; g++) { + for (g = MIN_IMA_GOLOMB_PAR; g < MAX_IMA_GOLOMB_PAR; g++) { uint32_t s = cmp_rdcu_get_good_spill(g, cfg->cmp_mode); cfg->golomb_par = g; @@ -171,8 +171,8 @@ static uint32_t brute_force(struct cmp_cfg *cfg) printf("0%%... "); fflush(stdout); - for (g = MIN_RDCU_GOLOMB_PAR; g < MAX_RDCU_GOLOMB_PAR; g++) { - for (s = MIN_RDCU_SPILL; s < cmp_rdcu_max_spill(g); s++) { + for (g = MIN_IMA_GOLOMB_PAR; g < MAX_IMA_GOLOMB_PAR; g++) { + for (s = MIN_IMA_SPILL; s < cmp_ima_max_spill(g); s++) { cfg->golomb_par = g; cfg->spill = s; @@ -211,11 +211,11 @@ static uint32_t brute_force(struct cmp_cfg *cfg) static void add_rdcu_pars_internal(struct cmp_cfg *cfg) { - if (cfg->golomb_par == MIN_RDCU_GOLOMB_PAR) { + if (cfg->golomb_par == MIN_IMA_GOLOMB_PAR) { cfg->ap1_golomb_par = cfg->golomb_par + 1; cfg->ap2_golomb_par = cfg->golomb_par + 2; - } else if (cfg->golomb_par == MAX_RDCU_GOLOMB_PAR) { + } else if (cfg->golomb_par == MAX_IMA_GOLOMB_PAR) { cfg->ap1_golomb_par = cfg->golomb_par - 2; cfg->ap2_golomb_par = cfg->golomb_par - 1; } else { diff --git a/lib/cmp_icu.c b/lib/cmp_icu.c index a05a98f..79d1a64 100644 --- a/lib/cmp_icu.c +++ b/lib/cmp_icu.c @@ -79,7 +79,6 @@ struct encoder_setupt { struct cmp_cfg cmp_cfg_icu_create(enum cmp_data_type data_type, enum cmp_mode cmp_mode, uint32_t model_value, uint32_t lossy_par) { - int cfg_valid; struct cmp_cfg cfg; memset(&cfg, 0, sizeof(cfg)); @@ -89,8 +88,7 @@ struct cmp_cfg cmp_cfg_icu_create(enum cmp_data_type data_type, enum cmp_mode cm cfg.model_value = model_value; cfg.round = lossy_par; - cfg_valid = cmp_cfg_icu_gen_par_is_valid(&cfg); - if (!cfg_valid) + if (cmp_cfg_icu_gen_par_is_invalid(&cfg)) cfg.data_type = DATA_TYPE_UNKNOWN; return cfg; @@ -138,7 +136,7 @@ uint32_t cmp_cfg_icu_buffers(struct cmp_cfg *cfg, void *data_to_compress, cfg->icu_output_buf = compressed_data; cfg->buffer_length = compressed_data_len_samples; - if (!cmp_cfg_icu_buffers_is_valid(cfg)) + if (cmp_cfg_icu_buffers_is_invalid(cfg)) return 0; data_size = cmp_cal_size_of_data(compressed_data_len_samples, cfg->data_type); @@ -173,7 +171,7 @@ int cmp_cfg_icu_imagette(struct cmp_cfg *cfg, uint32_t cmp_par, cfg->golomb_par = cmp_par; cfg->spill = spillover_par; - if (!cmp_cfg_imagette_is_valid(cfg)) + if (cmp_cfg_imagette_is_invalid(cfg, ICU_CHECK)) return -1; return 0; @@ -227,7 +225,7 @@ int cmp_cfg_fx_cob(struct cmp_cfg *cfg, cfg->spill_ecob = spillover_ecob; cfg->spill_fx_cob_variance = spillover_fx_cob_variance; - if (!cmp_cfg_fx_cob_is_valid(cfg)) + if (cmp_cfg_fx_cob_is_invalid(cfg)) return -1; return 0; @@ -268,7 +266,7 @@ int cmp_cfg_aux(struct cmp_cfg *cfg, cfg->spill_variance = spillover_variance; cfg->spill_pixels_error = spillover_pixels_error; - if (!cmp_cfg_aux_is_valid(cfg)) + if (cmp_cfg_aux_is_invalid(cfg)) return -1; return 0; @@ -2250,7 +2248,10 @@ static int pad_bitstream(const struct cmp_cfg *cfg, int cmp_size) if (cmp_size < 0) return cmp_size; - /* no padding in RAW mode; DIFFERENCE ENDIANNESS */ + if (!cfg->icu_output_buf) + return cmp_size; + + /* no padding in RAW mode; ALWAYS BIG-ENDIAN */ if (cfg->cmp_mode == CMP_MODE_RAW) return cmp_size; @@ -2288,6 +2289,9 @@ static int cmp_data_to_big_endian(const struct cmp_cfg *cfg, int cmp_size) if (cmp_size < 0) return cmp_size; + if (!cfg->icu_output_buf) + return cmp_size; + if (cfg->cmp_mode == CMP_MODE_RAW) { if (s & 0x7) /* size must be a multiple of 8 in RAW mode */ return -1; @@ -2343,7 +2347,7 @@ int icu_compress_data(const struct cmp_cfg *cfg) if (cfg->samples > cfg->buffer_length) return CMP_ERROR_SMALL_BUF; - if (!cmp_cfg_is_valid(cfg)) + if (cmp_cfg_is_invalid(cfg)) return -1; if (raw_mode_is_used(cfg->cmp_mode)) { @@ -2425,10 +2429,8 @@ int icu_compress_data(const struct cmp_cfg *cfg) /* LCOV_EXCL_STOP */ } - if (cfg->icu_output_buf) { - cmp_size = pad_bitstream(cfg, cmp_size); - cmp_size = cmp_data_to_big_endian(cfg, cmp_size); - } + cmp_size = pad_bitstream(cfg, cmp_size); + cmp_size = cmp_data_to_big_endian(cfg, cmp_size); return cmp_size; } diff --git a/lib/cmp_rdcu.c b/lib/cmp_rdcu.c index c3ffb7c..6a73545 100644 --- a/lib/cmp_rdcu.c +++ b/lib/cmp_rdcu.c @@ -413,60 +413,60 @@ static int rdcu_cfg_imagette_is_invalid(const struct cmp_cfg *cfg) { int cfg_invalid = 0; - if (cfg->golomb_par < MIN_RDCU_GOLOMB_PAR || - cfg->golomb_par > MAX_RDCU_GOLOMB_PAR) { - debug_print("Error: The selected Golomb parameter: %u is not supported. The Golomb parameter has to be between [%u, %u].\n", - cfg->golomb_par, MIN_RDCU_GOLOMB_PAR, MAX_RDCU_GOLOMB_PAR); + if (cfg->golomb_par < MIN_IMA_GOLOMB_PAR || + cfg->golomb_par > MAX_IMA_GOLOMB_PAR) { + debug_print("Error: The selected Golomb parameter: %u is not supported. The Golomb parameter has to be between [%u, %u].\n", + cfg->golomb_par, MIN_IMA_GOLOMB_PAR, MAX_IMA_GOLOMB_PAR); cfg_invalid++; } - if (cfg->ap1_golomb_par < MIN_RDCU_GOLOMB_PAR || - cfg->ap1_golomb_par > MAX_RDCU_GOLOMB_PAR) { - debug_print("Error: The selected adaptive 1 Golomb parameter: %u is not supported. The Golomb parameter has to be between [%u, %u].\n", - cfg->ap1_golomb_par, MIN_RDCU_GOLOMB_PAR, MAX_RDCU_GOLOMB_PAR); + if (cfg->ap1_golomb_par < MIN_IMA_GOLOMB_PAR || + cfg->ap1_golomb_par > MAX_IMA_GOLOMB_PAR) { + debug_print("Error: The selected adaptive 1 Golomb parameter: %u is not supported. The Golomb parameter has to be between [%u, %u].\n", + cfg->ap1_golomb_par, MIN_IMA_GOLOMB_PAR, MAX_IMA_GOLOMB_PAR); cfg_invalid++; } - if (cfg->ap2_golomb_par < MIN_RDCU_GOLOMB_PAR || - cfg->ap2_golomb_par > MAX_RDCU_GOLOMB_PAR) { + if (cfg->ap2_golomb_par < MIN_IMA_GOLOMB_PAR || + cfg->ap2_golomb_par > MAX_IMA_GOLOMB_PAR) { debug_print("Error: The selected adaptive 2 Golomb parameter: %u is not supported. The Golomb parameter has to be between [%u, %u].\n", - cfg->ap2_golomb_par, MIN_RDCU_GOLOMB_PAR, MAX_RDCU_GOLOMB_PAR); + cfg->ap2_golomb_par, MIN_IMA_GOLOMB_PAR, MAX_IMA_GOLOMB_PAR); cfg_invalid++; } - if (cfg->spill < MIN_RDCU_SPILL) { + if (cfg->spill < MIN_IMA_SPILL) { debug_print("Error: The selected spillover threshold value: %u is too small. Smallest possible spillover value is: %u.\n", - cfg->spill, MIN_RDCU_SPILL); + cfg->spill, MIN_IMA_SPILL); cfg_invalid++; } - if (cfg->spill > cmp_rdcu_max_spill(cfg->golomb_par)) { + if (cfg->spill > cmp_ima_max_spill(cfg->golomb_par)) { debug_print("Error: The selected spillover threshold value: %u is too large for the selected Golomb parameter: %u, the largest possible spillover value is: %u.\n", - cfg->spill, cfg->golomb_par, cmp_rdcu_max_spill(cfg->golomb_par)); + cfg->spill, cfg->golomb_par, cmp_ima_max_spill(cfg->golomb_par)); cfg_invalid++; } - if (cfg->ap1_spill < MIN_RDCU_SPILL) { + if (cfg->ap1_spill < MIN_IMA_SPILL) { debug_print("Error: The selected adaptive 1 spillover threshold value: %u is too small. Smallest possible spillover value is: %u.\n", - cfg->ap1_spill, MIN_RDCU_SPILL); + cfg->ap1_spill, MIN_IMA_SPILL); cfg_invalid++; } - if (cfg->ap1_spill > cmp_rdcu_max_spill(cfg->ap1_golomb_par)) { + if (cfg->ap1_spill > cmp_ima_max_spill(cfg->ap1_golomb_par)) { debug_print("Error: The selected adaptive 1 spillover threshold value: %u is too large for the selected adaptive 1 Golomb parameter: %u, the largest possible adaptive 1 spillover value is: %u.\n", - cfg->ap1_spill, cfg->ap1_golomb_par, cmp_rdcu_max_spill(cfg->ap1_golomb_par)); + cfg->ap1_spill, cfg->ap1_golomb_par, cmp_ima_max_spill(cfg->ap1_golomb_par)); cfg_invalid++; } - if (cfg->ap2_spill < MIN_RDCU_SPILL) { + if (cfg->ap2_spill < MIN_IMA_SPILL) { debug_print("Error: The selected adaptive 2 spillover threshold value: %u is too small. Smallest possible spillover value is: %u.\n", - cfg->ap2_spill, MIN_RDCU_SPILL); + cfg->ap2_spill, MIN_IMA_SPILL); cfg_invalid++; } - if (cfg->ap2_spill > cmp_rdcu_max_spill(cfg->ap2_golomb_par)) { + if (cfg->ap2_spill > cmp_ima_max_spill(cfg->ap2_golomb_par)) { debug_print("Error: The selected adaptive 2 spillover threshold value: %u is too large for the selected adaptive 2 Golomb parameter: %u, the largest possible adaptive 2 spillover value is: %u.\n", - cfg->ap2_spill, cfg->ap2_golomb_par, cmp_rdcu_max_spill(cfg->ap2_golomb_par)); + cfg->ap2_spill, cfg->ap2_golomb_par, cmp_ima_max_spill(cfg->ap2_golomb_par)); cfg_invalid++; } diff --git a/lib/cmp_support.c b/lib/cmp_support.c index 5572a90..17deeed 100644 --- a/lib/cmp_support.c +++ b/lib/cmp_support.c @@ -389,8 +389,8 @@ unsigned int cmp_up_model(unsigned int data, unsigned int model, /** - * @brief get the maximum valid spill threshold value for a RDCU HW compression - * in diff or model mode + * @brief get the maximum valid spill threshold value for a RDCU HW imagette + * compression in diff or model mode * * @param golomb_par Golomb parameter * @@ -398,10 +398,10 @@ unsigned int cmp_up_model(unsigned int data, unsigned int model, * mode compression; 0 if golomb_par is invalid */ -uint32_t cmp_rdcu_max_spill(unsigned int golomb_par) +uint32_t cmp_ima_max_spill(unsigned int golomb_par) { /* the RDCU can only generate 16 bit long code words -> lower max spill needed */ - const uint32_t LUT_MAX_RDCU[MAX_RDCU_GOLOMB_PAR+1] = { 0, 8, 22, 35, 48, + const uint32_t LUT_MAX_RDCU[MAX_IMA_GOLOMB_PAR+1] = { 0, 8, 22, 35, 48, 60, 72, 84, 96, 107, 118, 129, 140, 151, 162, 173, 184, 194, 204, 214, 224, 234, 244, 254, 264, 274, 284, 294, 304, 314, 324, 334, 344, 353, 362, 371, 380, 389, 398, 407, 416, 425, 434, 443, @@ -409,7 +409,7 @@ uint32_t cmp_rdcu_max_spill(unsigned int golomb_par) 569, 578, 587, 596, 605, 614, 623 }; - if (golomb_par > MAX_RDCU_GOLOMB_PAR) + if (golomb_par > MAX_IMA_GOLOMB_PAR) return 0; return LUT_MAX_RDCU[golomb_par]; @@ -458,14 +458,14 @@ unsigned int cmp_bit_to_4byte(unsigned int cmp_size_bit) /** * @brief check if the compression data type, compression mode, model value and - * the lossy rounding parameters are valid for a ICU compression + * the lossy rounding parameters are invalid for a ICU compression * * @param cfg pointer to the compressor configuration * - * @returns 1 if generic compression parameters are valid, otherwise 0 + * @returns 0 if generic compression parameters are valid, otherwise invalid */ -int cmp_cfg_icu_gen_par_is_valid(const struct cmp_cfg *cfg) +int cmp_cfg_icu_gen_par_is_invalid(const struct cmp_cfg *cfg) { int cfg_invalid = 0; @@ -496,22 +496,19 @@ int cmp_cfg_icu_gen_par_is_valid(const struct cmp_cfg *cfg) cfg_invalid++; } - if (cfg_invalid) - return 0; - - return 1; + return cfg_invalid; } /** - * @brief check if the buffer parameters are valid + * @brief check if the buffer parameters are invalid * * @param cfg pointer to the compressor configuration * - * @returns 1 if the buffer parameters are valid, otherwise 0 + * @returns 0 if the buffer parameters are valid, otherwise invalid */ -int cmp_cfg_icu_buffers_is_valid(const struct cmp_cfg *cfg) +int cmp_cfg_icu_buffers_is_invalid(const struct cmp_cfg *cfg) { int cfg_invalid = 0; @@ -572,65 +569,81 @@ int cmp_cfg_icu_buffers_is_valid(const struct cmp_cfg *cfg) } } - if (cfg_invalid) - return 0; - - return 1; + return cfg_invalid; } /** - * @brief check if the combination of the different compression parameters is valid + * @brief check if the combination of the different compression parameters is invalid * * @param cmp_par compression parameter * @param spill spillover threshold parameter * @param cmp_mode compression mode + * @param data_type compression data type * @param par_name string describing the use of the compression par. for * debug messages (can be NULL) * - * @returns 1 if the parameter combination is valid, otherwise 0 + * @returns 0 if the parameter combination is valid, otherwise the combination is invalid */ -static int cmp_pars_are_valid(uint32_t cmp_par, uint32_t spill, enum cmp_mode cmp_mode, - char *par_name) +static int cmp_pars_are_invalid(uint32_t cmp_par, uint32_t spill, enum cmp_mode cmp_mode, + enum cmp_data_type data_type, char *par_name) { int cfg_invalid = 0; + uint32_t min_golomb_par; + uint32_t max_golomb_par; + uint32_t min_spill; + uint32_t max_spill; if (!par_name) par_name = ""; + /* The maximum compression parameter for imagette data are smaller to + * fit into the imagette compression entity header */ + if (cmp_imagette_data_type_is_used(data_type)) { + min_golomb_par = MIN_IMA_GOLOMB_PAR; + max_golomb_par = MAX_IMA_GOLOMB_PAR; + min_spill = MIN_IMA_SPILL; + max_spill = cmp_ima_max_spill(cmp_par); + } else { + min_golomb_par = MIN_ICU_GOLOMB_PAR; + max_golomb_par = MAX_ICU_GOLOMB_PAR; + min_spill = MIN_ICU_SPILL; + max_spill = cmp_icu_max_spill(cmp_par); + } + + switch (cmp_mode) { case CMP_MODE_RAW: /* no checks needed */ break; - case CMP_MODE_STUFF: - if (cmp_par > MAX_STUFF_CMP_PAR) { - debug_print("Error: The selected %s stuff mode compression parameter: %u is too large, the largest possible value in the selected compression mode is: %u.\n", - par_name, cmp_par, MAX_STUFF_CMP_PAR); - cfg_invalid++; - } - break; case CMP_MODE_DIFF_ZERO: case CMP_MODE_DIFF_MULTI: case CMP_MODE_MODEL_ZERO: case CMP_MODE_MODEL_MULTI: - if (cmp_par < MIN_ICU_GOLOMB_PAR || - cmp_par > MAX_ICU_GOLOMB_PAR) { + if (cmp_par < min_golomb_par || cmp_par > max_golomb_par) { debug_print("Error: The selected %s compression parameter: %u is not supported. The compression parameter has to be between [%u, %u].\n", - par_name, cmp_par, MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); + par_name, cmp_par, min_golomb_par, max_golomb_par); cfg_invalid++; } - if (spill < MIN_ICU_SPILL) { + if (spill < min_spill) { debug_print("Error: The selected %s spillover threshold value: %u is too small. Smallest possible spillover value is: %u.\n", - par_name, spill, MIN_ICU_SPILL); + par_name, spill, min_spill); cfg_invalid++; } - if (spill > cmp_icu_max_spill(cmp_par)) { + if (spill > max_spill) { debug_print("Error: The selected %s spillover threshold value: %u is too large for the selected %s compression parameter: %u, the largest possible spillover value in the selected compression mode is: %u.\n", - par_name, spill, par_name, cmp_par, cmp_icu_max_spill(cmp_par)); + par_name, spill, par_name, cmp_par, max_spill); cfg_invalid++; } + break; + case CMP_MODE_STUFF: + if (cmp_par > MAX_STUFF_CMP_PAR) { + debug_print("Error: The selected %s stuff mode compression parameter: %u is too large, the largest possible value in the selected compression mode is: %u.\n", + par_name, cmp_par, MAX_STUFF_CMP_PAR); + cfg_invalid++; + } break; default: debug_print("Error: The compression mode is not supported.\n"); @@ -638,24 +651,24 @@ static int cmp_pars_are_valid(uint32_t cmp_par, uint32_t spill, enum cmp_mode cm break; } - if (cfg_invalid) - return 0; - - return 1; + return cfg_invalid; } /** - * @brief check if the imagette specific compression parameters are valid + * @brief check if the imagette specific compression parameters are invalid * - * @param cfg pointer to the compressor configuration + * @param cfg pointer to the compressor configuration + * @param rdcu_check set to non-zero if a check for a imagette RDCU compression + * should be done; zero for imagette ICU compression * - * @returns 1 if the imagette specific parameters are valid, otherwise 0 + * @returns 0 if the imagette specific parameters are valid, otherwise invalid */ -int cmp_cfg_imagette_is_valid(const struct cmp_cfg *cfg) +int cmp_cfg_imagette_is_invalid(const struct cmp_cfg *cfg, int rdcu_check) { int cfg_invalid = 0; + enum cmp_mode cmp_mode; if (!cfg) return 0; @@ -665,35 +678,37 @@ int cmp_cfg_imagette_is_valid(const struct cmp_cfg *cfg) cfg_invalid++; } - if (!cmp_pars_are_valid(cfg->golomb_par, cfg->spill, cfg->cmp_mode, - "imagette")) - cfg_invalid++; - - if (cmp_ap_imagette_data_type_is_used(cfg->data_type)) { - if (!cmp_pars_are_valid(cfg->ap1_golomb_par, cfg->ap1_spill, - cfg->cmp_mode, "adaptive 1 imagette")) - cfg_invalid++; - if (!cmp_pars_are_valid(cfg->ap2_golomb_par, cfg->ap2_spill, - cfg->cmp_mode, "adaptive 2 imagette")) - cfg_invalid++; + /* The RDCU needs valid compression parameters also in RAW_MODE */ + if (rdcu_check && cfg->cmp_mode == CMP_MODE_RAW) + cmp_mode = CMP_MODE_MODEL_ZERO; + else + cmp_mode = cfg->cmp_mode; + + cfg_invalid += cmp_pars_are_invalid(cfg->golomb_par, cfg->spill, cmp_mode, + cfg->data_type, "imagette"); + + /* for the RDCU the adaptive parameters have to be always valid */ + if (rdcu_check || cmp_ap_imagette_data_type_is_used(cfg->data_type)) { + cfg_invalid += cmp_pars_are_invalid(cfg->ap1_golomb_par, cfg->ap1_spill, + cmp_mode, cfg->data_type, "adaptive 1 imagette"); + cfg_invalid += cmp_pars_are_invalid(cfg->ap2_golomb_par, cfg->ap2_spill, + cmp_mode, cfg->data_type, "adaptive 2 imagette"); } - if (cfg_invalid) - return 0; - - return 1; + return cfg_invalid; } /** - * @brief check if the flux/center of brightness specific compression parameters are valid + * @brief check if the flux/center of brightness specific compression parameters + * are invalid * * @param cfg pointer to the compressor configuration * - * @returns 1 if the flux/center of brightness specific parameters are valid, otherwise 0 + * @returns 0 if the flux/center of brightness specific parameters are valid, otherwise invalid */ -int cmp_cfg_fx_cob_is_valid(const struct cmp_cfg *cfg) +int cmp_cfg_fx_cob_is_invalid(const struct cmp_cfg *cfg) { int cfg_invalid = 0; int check_exp_flags = 0, check_ncob = 0, check_efx = 0, check_ecob = 0, check_var = 0; @@ -706,8 +721,8 @@ int cmp_cfg_fx_cob_is_valid(const struct cmp_cfg *cfg) cfg_invalid++; } /* flux parameter is needed for every fx_cob data_type */ - if (!cmp_pars_are_valid(cfg->cmp_par_fx, cfg->spill_fx, cfg->cmp_mode, "flux")) - cfg_invalid++; + cfg_invalid += cmp_pars_are_invalid(cfg->cmp_par_fx, cfg->spill_fx, + cfg->cmp_mode, cfg->data_type, "flux"); switch (cfg->data_type) { case DATA_TYPE_S_FX: @@ -766,39 +781,37 @@ int cmp_cfg_fx_cob_is_valid(const struct cmp_cfg *cfg) break; } - if (check_exp_flags && !cmp_pars_are_valid(cfg->cmp_par_exp_flags, - cfg->spill_exp_flags, cfg->cmp_mode, "exposure flags")) - cfg_invalid++; - if (check_ncob && !cmp_pars_are_valid(cfg->cmp_par_ncob, cfg->spill_ncob, - cfg->cmp_mode, "center of brightness")) - cfg_invalid++; - if (check_efx && !cmp_pars_are_valid(cfg->cmp_par_efx, cfg->spill_efx, - cfg->cmp_mode, "extended flux")) - cfg_invalid++; - if (check_ecob && !cmp_pars_are_valid(cfg->cmp_par_ecob, cfg->spill_ecob, - cfg->cmp_mode, "extended center of brightness")) - cfg_invalid++; - if (check_var && !cmp_pars_are_valid(cfg->cmp_par_fx_cob_variance, - cfg->spill_fx_cob_variance, cfg->cmp_mode, "flux COB varianc")) - cfg_invalid++; - - if (cfg_invalid) - return 0; - - return 1; + if (check_exp_flags) + cfg_invalid += cmp_pars_are_invalid(cfg->cmp_par_exp_flags, cfg->spill_exp_flags, + cfg->cmp_mode, cfg->data_type, "exposure flags"); + if (check_ncob) + cfg_invalid += cmp_pars_are_invalid(cfg->cmp_par_ncob, cfg->spill_ncob, + cfg->cmp_mode, cfg->data_type, "center of brightness"); + if (check_efx) + cfg_invalid += cmp_pars_are_invalid(cfg->cmp_par_efx, cfg->spill_efx, + cfg->cmp_mode, cfg->data_type, "extended flux"); + if (check_ecob) + cfg_invalid += cmp_pars_are_invalid(cfg->cmp_par_ecob, cfg->spill_ecob, + cfg->cmp_mode, cfg->data_type, "extended center of brightness"); + if (check_var) + cfg_invalid += cmp_pars_are_invalid(cfg->cmp_par_fx_cob_variance, + cfg->spill_fx_cob_variance, cfg->cmp_mode, cfg->data_type, "flux COB varianc"); + + return cfg_invalid; } /** - * @brief check if the auxiliary science specific compression parameters are valid + * @brief check if the auxiliary science specific compression parameters are invalid * * @param cfg pointer to the compressor configuration * - * @returns 1 if the auxiliary science specific parameters are valid, otherwise 0 + * @returns 0 if the auxiliary science specific parameters are valid, otherwise + * invalid * TODO: implemented DATA_TYPE_F_CAM_OFFSET and DATA_TYPE_F_CAM_BACKGROUND */ -int cmp_cfg_aux_is_valid(const struct cmp_cfg *cfg) +int cmp_cfg_aux_is_invalid(const struct cmp_cfg *cfg) { int cfg_invalid = 0; @@ -810,63 +823,49 @@ int cmp_cfg_aux_is_valid(const struct cmp_cfg *cfg) cfg_invalid++; } - if (!cmp_pars_are_valid(cfg->cmp_par_mean, cfg->spill_mean, - cfg->cmp_mode, "mean")) - cfg_invalid++; - if (!cmp_pars_are_valid(cfg->cmp_par_variance, cfg->spill_variance, - cfg->cmp_mode, "variance")) - cfg_invalid++; + cfg_invalid += cmp_pars_are_invalid(cfg->cmp_par_mean, cfg->spill_mean, + cfg->cmp_mode, cfg->data_type, "mean"); + cfg_invalid += cmp_pars_are_invalid(cfg->cmp_par_variance, cfg->spill_variance, + cfg->cmp_mode, cfg->data_type, "variance"); + /* if (cfg->data_type != DATA_TYPE_OFFSET && cfg->data_type != DATA_TYPE_F_CAM_OFFSET) */ if (cfg->data_type != DATA_TYPE_OFFSET) - if (!cmp_pars_are_valid(cfg->cmp_par_pixels_error, cfg->spill_pixels_error, - cfg->cmp_mode, "outlier pixls num")) - cfg_invalid++; - - if (cfg_invalid) - return 0; + cfg_invalid += cmp_pars_are_invalid(cfg->cmp_par_pixels_error, cfg->spill_pixels_error, + cfg->cmp_mode, cfg->data_type, "outlier pixls num"); - return 1; + return cfg_invalid; } /** - * @brief check if a compression configuration is valid + * @brief check if a compression configuration is invalid * * @param cfg pointer to the compressor configuration * - * @returns 1 if the compression configuration is valid, otherwise 0 + * @returns 0 if the compression configuration is valid, otherwise invalid */ -int cmp_cfg_is_valid(const struct cmp_cfg *cfg) +int cmp_cfg_is_invalid(const struct cmp_cfg *cfg) { int cfg_invalid = 0; if (!cfg) return 0; - if (!cmp_cfg_icu_gen_par_is_valid(cfg)) - cfg_invalid++; + cfg_invalid += cmp_cfg_icu_gen_par_is_invalid(cfg); - if (!cmp_cfg_icu_buffers_is_valid(cfg)) - cfg_invalid++; + cfg_invalid += cmp_cfg_icu_buffers_is_invalid(cfg); - if (cmp_imagette_data_type_is_used(cfg->data_type)) { - if (!cmp_cfg_imagette_is_valid(cfg)) - cfg_invalid++; - } else if (cmp_fx_cob_data_type_is_used(cfg->data_type)) { - if (!cmp_cfg_fx_cob_is_valid(cfg)) - cfg_invalid++; - } else if (cmp_aux_data_type_is_used(cfg->data_type)) { - if (!cmp_cfg_aux_is_valid(cfg)) - cfg_invalid++; - } else { + if (cmp_imagette_data_type_is_used(cfg->data_type)) + cfg_invalid += cmp_cfg_imagette_is_invalid(cfg, ICU_CHECK); + else if (cmp_fx_cob_data_type_is_used(cfg->data_type)) + cfg_invalid += cmp_cfg_fx_cob_is_invalid(cfg); + else if (cmp_aux_data_type_is_used(cfg->data_type)) + cfg_invalid += cmp_cfg_aux_is_invalid(cfg); + else cfg_invalid++; - } - - if (cfg_invalid) - return 0; - return 1; + return cfg_invalid; } diff --git a/test/cmp_icu/test_cmp_icu.c b/test/cmp_icu/test_cmp_icu.c index 3eae4be..65ab545 100644 --- a/test/cmp_icu/test_cmp_icu.c +++ b/test/cmp_icu/test_cmp_icu.c @@ -1,6 +1,5 @@ -#include "cmp_support.h" -#include <string.h> #include <stdlib.h> + #if defined __has_include # if __has_include(<time.h>) # include <time.h> @@ -436,8 +435,8 @@ void test_cmp_cfg_icu_imagette(void) /* lowest values 1d/model mode */ cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_MODEL_ZERO, 0, CMP_LOSSLESS); - cmp_par = MIN_ICU_GOLOMB_PAR; - spillover_par = MIN_ICU_SPILL; + cmp_par = MIN_IMA_GOLOMB_PAR; + spillover_par = MIN_IMA_SPILL; error = cmp_cfg_icu_imagette(&cfg, cmp_par, spillover_par); TEST_ASSERT_FALSE(error); TEST_ASSERT_EQUAL_INT(cfg.golomb_par, 1); @@ -445,12 +444,12 @@ void test_cmp_cfg_icu_imagette(void) /* highest values 1d/model mode */ cfg = cmp_cfg_icu_create(DATA_TYPE_F_CAM_IMAGETTE, CMP_MODE_DIFF_MULTI, 16, CMP_LOSSLESS); - cmp_par = MAX_ICU_GOLOMB_PAR; - spillover_par = cmp_icu_max_spill(cmp_par); + cmp_par = MAX_IMA_GOLOMB_PAR; + spillover_par = cmp_ima_max_spill(cmp_par); error = cmp_cfg_icu_imagette(&cfg, cmp_par, spillover_par); TEST_ASSERT_FALSE(error); - TEST_ASSERT_EQUAL_INT(cfg.golomb_par, 0xFFFF); - TEST_ASSERT_EQUAL_INT(cfg.spill, 1048545); + TEST_ASSERT_EQUAL_INT(cfg.golomb_par, MAX_IMA_GOLOMB_PAR); + TEST_ASSERT_EQUAL_INT(cfg.spill, cmp_ima_max_spill(MAX_IMA_GOLOMB_PAR)); /* wrong data type test */ for (data_type = 0; data_type <= DATA_TYPE_F_CAM_BACKGROUND; data_type++) { @@ -461,8 +460,8 @@ void test_cmp_cfg_icu_imagette(void) data_type == DATA_TYPE_F_CAM_IMAGETTE) { TEST_ASSERT_FALSE(error); TEST_ASSERT_EQUAL_INT(data_type, cfg.data_type); - TEST_ASSERT_EQUAL_INT(cfg.golomb_par, 0xFFFF); - TEST_ASSERT_EQUAL_INT(cfg.spill, 1048545); + TEST_ASSERT_EQUAL_INT(cfg.golomb_par, MAX_IMA_GOLOMB_PAR); + TEST_ASSERT_EQUAL_INT(cfg.spill, cmp_ima_max_spill(MAX_IMA_GOLOMB_PAR)); } else { TEST_ASSERT_TRUE(error); } @@ -472,8 +471,8 @@ void test_cmp_cfg_icu_imagette(void) /* cmp_par to big */ cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_MODEL_MULTI, 16, CMP_LOSSLESS); - cmp_par = MAX_ICU_GOLOMB_PAR + 1; - spillover_par = MIN_ICU_SPILL; + cmp_par = MAX_IMA_GOLOMB_PAR + 1; + spillover_par = MIN_IMA_SPILL; error = cmp_cfg_icu_imagette(&cfg, cmp_par, spillover_par); TEST_ASSERT_TRUE(error); /* ignore in RAW MODE */ @@ -483,8 +482,8 @@ void test_cmp_cfg_icu_imagette(void) /* cmp_par to small */ cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_MODEL_MULTI, 16, CMP_LOSSLESS); - cmp_par = MIN_ICU_GOLOMB_PAR - 1; - spillover_par = MIN_ICU_SPILL; + cmp_par = MIN_IMA_GOLOMB_PAR - 1; + spillover_par = MIN_IMA_SPILL; error = cmp_cfg_icu_imagette(&cfg, cmp_par, spillover_par); TEST_ASSERT_TRUE(error); /* ignore in RAW MODE */ @@ -494,8 +493,8 @@ void test_cmp_cfg_icu_imagette(void) /* spillover_par to big */ cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_MODEL_MULTI, 16, CMP_LOSSLESS); - cmp_par = MIN_ICU_GOLOMB_PAR; - spillover_par = cmp_icu_max_spill(cmp_par)+1; + cmp_par = MIN_IMA_GOLOMB_PAR; + spillover_par = cmp_ima_max_spill(cmp_par)+1; error = cmp_cfg_icu_imagette(&cfg, cmp_par, spillover_par); TEST_ASSERT_TRUE(error); /* ignore in RAW MODE */ @@ -505,8 +504,8 @@ void test_cmp_cfg_icu_imagette(void) /* spillover_par to small */ cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_DIFF_ZERO, 0, CMP_LOSSLESS); - cmp_par = MAX_ICU_GOLOMB_PAR; - spillover_par = MIN_ICU_SPILL -1 ; + cmp_par = MAX_IMA_GOLOMB_PAR; + spillover_par = MIN_IMA_SPILL -1 ; error = cmp_cfg_icu_imagette(&cfg, cmp_par, spillover_par); TEST_ASSERT_TRUE(error); /* ignore in RAW MODE */ diff --git a/test/cmp_icu/test_decmp.c b/test/cmp_icu/test_decmp.c index cb00bb0..244bbe8 100644 --- a/test/cmp_icu/test_decmp.c +++ b/test/cmp_icu/test_decmp.c @@ -49,9 +49,8 @@ size_t icu_compress_data_entity(struct cmp_entity *ent, const struct cmp_cfg *cf if (cmp_size_bits < 0) return 0; - /* XXX overwrite the size of the compression entity with the size of the - * actual size of the compressed data */ - /* not all allocated memory is normally needed */ + /* XXX overwrite the size of the compression entity with the size of the actual + * size of the compressed data; not all allocated memory is normally used */ s = cmp_ent_create(ent, cfg->data_type, cfg->cmp_mode == CMP_MODE_RAW, cmp_bit_to_4byte(cmp_size_bits)); @@ -161,9 +160,28 @@ void test_re_map_to_pos(void) } +void test_rice_decoder(void) +{ + int cw_len; + uint32_t code_word; + unsigned int m = ~0;/* we don't need this value */ + unsigned int log2_m; + unsigned int decoded_cw; + + /* test log_2 to big */ + code_word = 0xE0000000; + log2_m = 33; + cw_len = rice_decoder(code_word, m, log2_m, &decoded_cw); + TEST_ASSERT_EQUAL(0, cw_len); + log2_m = UINT_MAX; + cw_len = rice_decoder(code_word, m, log2_m, &decoded_cw); + TEST_ASSERT_EQUAL(0, cw_len); +} + + void test_decode_normal(void) { - uint32_t decoded_value; + uint32_t decoded_value = ~0; int stream_pos, sample; /* compressed data from 0 to 6; */ uint32_t cmp_data[] = {0x5BBDF7E0}; @@ -176,7 +194,6 @@ void test_decode_normal(void) setup.encoder_par2 = ilog_2(setup.encoder_par1); setup.bitstream_adr = cmp_data; setup.max_stream_len = 32; - setup.max_cw_len = 16; stream_pos = 0; for (sample = 0; sample < 7; sample++) { @@ -190,7 +207,7 @@ void test_decode_normal(void) void test_decode_zero(void) { - uint32_t decoded_value; + uint32_t decoded_value = ~0; int stream_pos; uint32_t cmp_data[] = {0x88449FE0}; struct decoder_setup setup = {0}; @@ -221,7 +238,7 @@ void test_decode_zero(void) void test_decode_multi(void) { - uint32_t decoded_value; + uint32_t decoded_value = ~0; int stream_pos; uint32_t cmp_data[] = {0x16B66DF8, 0x84360000}; struct decoder_setup setup = {0}; @@ -297,7 +314,6 @@ void test_decompress_imagette_model(void) -#define CMP_PAR_UNUSED 0 /*TODO: remove this*/ #define DATA_SAMPLES 5 void test_cmp_decmp_s_fx_diff(void) { @@ -471,8 +487,8 @@ void test_imagette_random(void) NULL, compressed_data_len_samples); TEST_ASSERT_TRUE(cmp_data_size); - uint32_t golomb_par = my_random(MIN_RDCU_GOLOMB_PAR, MAX_RDCU_GOLOMB_PAR); - uint32_t max_spill = cmp_icu_max_spill(golomb_par); + uint32_t golomb_par = my_random(MIN_IMA_GOLOMB_PAR, MAX_IMA_GOLOMB_PAR); + uint32_t max_spill = cmp_ima_max_spill(golomb_par); TEST_ASSERT(max_spill > 1); uint32_t spill = my_random(2, max_spill); @@ -663,9 +679,9 @@ void test_random_compression_decompression(void) /* cfg.round = my_random(0,3); /1* XXX *1/ */ cfg.round = 0; - cfg.golomb_par = my_random(MIN_RDCU_GOLOMB_PAR, MAX_RDCU_GOLOMB_PAR); - cfg.ap1_golomb_par = my_random(MIN_RDCU_GOLOMB_PAR, MAX_RDCU_GOLOMB_PAR); - cfg.ap2_golomb_par = my_random(MIN_RDCU_GOLOMB_PAR, MAX_RDCU_GOLOMB_PAR); + cfg.golomb_par = my_random(MIN_IMA_GOLOMB_PAR, MAX_IMA_GOLOMB_PAR); + cfg.ap1_golomb_par = my_random(MIN_IMA_GOLOMB_PAR, MAX_IMA_GOLOMB_PAR); + cfg.ap2_golomb_par = my_random(MIN_IMA_GOLOMB_PAR, MAX_IMA_GOLOMB_PAR); cfg.cmp_par_exp_flags = my_random(MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); cfg.cmp_par_fx = my_random(MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); cfg.cmp_par_ncob = my_random(MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); @@ -676,9 +692,9 @@ void test_random_compression_decompression(void) cfg.cmp_par_variance = my_random(MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); cfg.cmp_par_pixels_error = my_random(MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); - cfg.spill = my_random(MIN_RDCU_SPILL, cmp_icu_max_spill(cfg.golomb_par)); - cfg.ap1_spill = my_random(MIN_RDCU_SPILL, cmp_icu_max_spill(cfg.ap1_golomb_par)); - cfg.ap2_spill = my_random(MIN_RDCU_SPILL, cmp_icu_max_spill(cfg.ap2_golomb_par)); + cfg.spill = my_random(MIN_IMA_SPILL, cmp_ima_max_spill(cfg.golomb_par)); + cfg.ap1_spill = my_random(MIN_IMA_SPILL, cmp_ima_max_spill(cfg.ap1_golomb_par)); + cfg.ap2_spill = my_random(MIN_IMA_SPILL, cmp_ima_max_spill(cfg.ap2_golomb_par)); if (!rdcu_supported_data_type_is_used(cfg.data_type)) { cfg.spill_exp_flags = my_random(MIN_ICU_SPILL, cmp_icu_max_spill(cfg.cmp_par_exp_flags)); cfg.spill_fx = my_random(MIN_ICU_SPILL, cmp_icu_max_spill(cfg.cmp_par_fx)); @@ -734,3 +750,9 @@ void test_random_compression_decompression(void) } } + +void test_decompression_error_cases(void) +{ + /* error cases model decompression without a model Buffer */ + /* error cases wrong cmp parameter; model value; usw */ +} -- GitLab From 67619fa739c5eac69c30cfd65e66814117650f8f Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Mon, 26 Sep 2022 11:55:22 +0200 Subject: [PATCH 37/46] some code formatting --- lib/cmp_data_types.c | 3 ++- lib/cmp_io.c | 10 +++++----- lib/decmp.c | 3 +-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/cmp_data_types.c b/lib/cmp_data_types.c index ee0131f..ed7d5e6 100644 --- a/lib/cmp_data_types.c +++ b/lib/cmp_data_types.c @@ -99,7 +99,8 @@ struct cmp_max_used_bits cmp_get_max_used_bits(void) * @returns version of the max used bits registry */ -uint8_t cmp_get_max_used_bits_version(void) { +uint8_t cmp_get_max_used_bits_version(void) +{ return max_used_bits.version; } diff --git a/lib/cmp_io.c b/lib/cmp_io.c index be0f8ed..d8772fd 100644 --- a/lib/cmp_io.c +++ b/lib/cmp_io.c @@ -414,7 +414,7 @@ enum cmp_data_type string2data_type(const char *data_type_str) if (isalpha(data_type_str[0])) { /* check if mode is given as text */ size_t j; - for (j = 0; j < sizeof(data_type_string_table) / sizeof(data_type_string_table[0]); j++) { + for (j = 0; j < sizeof(data_type_string_table) / sizeof(data_type_string_table[0]); j++) { if (!strcmp(data_type_str, data_type_string_table[j].str)) { data_type = data_type_string_table[j].data_type; break; @@ -447,7 +447,7 @@ const char *data_type2string(enum cmp_data_type data_type) size_t j; const char *string = "DATA_TYPE_UNKNOWN"; - for (j = 0; j < sizeof(data_type_string_table) / sizeof(data_type_string_table[0]); j++) { + for (j = 0; j < sizeof(data_type_string_table) / sizeof(data_type_string_table[0]); j++) { if (data_type == data_type_string_table[j].data_type) { string = data_type_string_table[j].str; break; @@ -472,7 +472,7 @@ int cmp_mode_parse(const char *cmp_mode_str, uint32_t *cmp_mode) { size_t j; static const struct { - uint32_t cmp_mode; + uint32_t cmp_mode; const char *str; } conversion[] = { {CMP_MODE_RAW, "MODE_RAW"}, @@ -493,7 +493,7 @@ int cmp_mode_parse(const char *cmp_mode_str, uint32_t *cmp_mode) return -1; if (isalpha(cmp_mode_str[0])) { /* check if mode is given as text */ - for (j = 0; j < sizeof(conversion) / sizeof(conversion[0]); ++j) { + for (j = 0; j < sizeof(conversion) / sizeof(conversion[0]); ++j) { if (!strcmp(cmp_mode_str, conversion[j].str)) { *cmp_mode = conversion[j].cmp_mode; return 0; @@ -825,7 +825,7 @@ int read_cmp_cfg(const char *file_name, struct cmp_cfg *cfg, int verbose_en) /** - * @brief parse a file containing a decompression information + * @brief parse a file containing a decompression information * @note internal use only! * * @param fp FILE pointer diff --git a/lib/decmp.c b/lib/decmp.c index da75d50..c266e64 100644 --- a/lib/decmp.c +++ b/lib/decmp.c @@ -148,8 +148,7 @@ static int rice_decoder(uint32_t code_word, unsigned int m, unsigned int log2_m, */ static int golomb_decoder(unsigned int code_word, unsigned int m, - unsigned int log2_m, unsigned int - *decoded_cw) + unsigned int log2_m, unsigned int *decoded_cw) { unsigned int q; /* quotient code */ unsigned int r1; /* remainder code group 1 */ -- GitLab From 7ee4798b5700e7696c9e51df6c06a8bfb9e8e322 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Mon, 26 Sep 2022 11:59:12 +0200 Subject: [PATCH 38/46] Add compression decompression test --- test/cmp_icu/meson.build | 18 +- test/cmp_icu/test_cmp_decmp.c | 614 ++++++++++++++++++++++++++++++++++ test/meson.build | 1 + 3 files changed, 631 insertions(+), 2 deletions(-) create mode 100644 test/cmp_icu/test_cmp_decmp.c diff --git a/test/cmp_icu/meson.build b/test/cmp_icu/meson.build index 405d376..7c9b356 100644 --- a/test/cmp_icu/meson.build +++ b/test/cmp_icu/meson.build @@ -12,10 +12,24 @@ test_cmp_icu = executable('test_cmp_icu', test('cmp_icu Unit Tests', test_cmp_icu) +test_case = files('test_cmp_decmp.c') +test_runner = test_runner_generator.process(test_case) + +test_cmp_decmp = executable('test_cmp_decmp', + test_case, test_runner, + include_directories : incdir, + link_with : cmp_lib, + dependencies : unity_dep, + build_by_default : false +) + +test('Compression Decompression Unit Tests', test_cmp_decmp) + + test_case = files('test_decmp.c') test_runner = test_runner_generator.process(test_case) -test_cmp_decomp = executable('test_cmp_decomp', +test_decmp = executable('test_decmp', test_case, test_runner, include_directories : incdir, link_with : cmp_lib, @@ -23,4 +37,4 @@ test_cmp_decomp = executable('test_cmp_decomp', build_by_default : false ) -test('Compression Decompression Unit Tests', test_cmp_decomp) +test('Decompression Unit Tests', test_decmp) diff --git a/test/cmp_icu/test_cmp_decmp.c b/test/cmp_icu/test_cmp_decmp.c new file mode 100644 index 0000000..e961ffc --- /dev/null +++ b/test/cmp_icu/test_cmp_decmp.c @@ -0,0 +1,614 @@ +/** + * @file test_cmp_decmp.c + * @author Dominik Loidolt (dominik.loidolt@univie.ac.at), + * @date 2022 + * + * @copyright GPLv2 + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * @brief random compression decompression test + * @detail We generate random data and compress them with random parameters. + * After that we put the data in a compression entity. We decompress the + * compression entity and compare the decompressed data with the original + * data. + */ + + +#include <string.h> +#include <stdlib.h> + +#include <unity.h> + +#include <cmp_icu.h> +#include <decmp.h> +#include <cmp_data_types.h> + +#if defined __has_include +# if __has_include(<time.h>) +# include <time.h> +# include <unistd.h> +# define HAS_TIME_H 1 +# endif +#endif + +#define IMAX_BITS(m) ((m)/((m)%255+1) / 255%255*8 + 7-86/((m)%255+12)) +#define RAND_MAX_WIDTH IMAX_BITS(RAND_MAX) + +#define set_n_bits(n) (~(~0UL << (n))) + + +/** + * @brief Seeds the pseudo-random number generator used by rand() + */ + +void setUp(void) +{ + unsigned int seed; + static int n; + +#if HAS_TIME_H + seed = time(NULL) * getpid(); +#else + seed = 1; +#endif + + if (!n) { + n = 1; + srand(seed); + printf("seed: %u\n", seed); + } +} + + +/** + * @brief generate a uint32_t random number + * + * @return a "random" uint32_t value + * @see https://stackoverflow.com/a/33021408 + */ + +uint32_t rand32(void) +{ + int i; + uint32_t r = 0; + + for (i = 0; i < 32; i += RAND_MAX_WIDTH) { + r <<= RAND_MAX_WIDTH; + r ^= (unsigned int) rand(); + } + return r; +} + + +/** + * @brief generate a random number in a range + * + * @param min minimum value (inclusive) + * @param max maximum value (inclusive) + * + * @returns "random" numbers in the range [min, max] + * + * @see https://c-faq.com/lib/randrange.html + */ + +uint32_t random_between(unsigned int min, unsigned int max) +{ + TEST_ASSERT(min < max); + if (max-min < RAND_MAX) + return min + rand() / (RAND_MAX / (max - min + 1ULL) + 1); + else + return min + rand32() / (UINT32_MAX / (max - min + 1ULL) + 1); +} + + +static void gen_ima_data(uint16_t *data, uint32_t samples) +{ + uint32_t i; + struct cmp_max_used_bits max_used_bits = cmp_get_max_used_bits(); + + for (i = 0; i < samples; i++) { + data[i] = random_between(0, set_n_bits(max_used_bits.nc_imagette)); + } +} + + +static void gen_offset_data(struct nc_offset *data, uint32_t samples) +{ + uint32_t i; + struct cmp_max_used_bits max_used_bits = cmp_get_max_used_bits(); + + for (i = 0; i < samples; i++) { + data[i].mean = random_between(0, set_n_bits(max_used_bits.nc_offset_mean)); + data[i].variance = random_between(0, set_n_bits(max_used_bits.nc_offset_variance)); + } +} + + +static void gen_background_data(struct nc_background *data, uint32_t samples) +{ + uint32_t i; + struct cmp_max_used_bits max_used_bits = cmp_get_max_used_bits(); + + for (i = 0; i < samples; i++) { + data[i].mean = random_between(0, set_n_bits(max_used_bits.nc_background_mean)); + data[i].variance = random_between(0, set_n_bits(max_used_bits.nc_background_variance)); + data[i].outlier_pixels = random_between(0, set_n_bits(max_used_bits.nc_background_outlier_pixels)); + } +} + + +static void gen_smearing_data(struct smearing *data, uint32_t samples) +{ + uint32_t i; + struct cmp_max_used_bits max_used_bits = cmp_get_max_used_bits(); + + for (i = 0; i < samples; i++) { + data[i].mean = random_between(0, set_n_bits(max_used_bits.smearing_mean)); + data[i].variance_mean = random_between(0, set_n_bits(max_used_bits.smearing_variance_mean)); + data[i].outlier_pixels = random_between(0, set_n_bits(max_used_bits.smearing_outlier_pixels)); + } +} + + +static void gen_s_fx_data(struct s_fx *data, uint32_t samples) +{ + uint32_t i; + struct cmp_max_used_bits max_used_bits = cmp_get_max_used_bits(); + + for (i = 0; i < samples; i++) { + data[i].exp_flags = random_between(0, set_n_bits(max_used_bits.s_exp_flags)); + data[i].fx = random_between(0, set_n_bits(max_used_bits.s_fx)); + } +} + + +static void gen_s_fx_efx_data(struct s_fx_efx *data, uint32_t samples) +{ + uint32_t i; + struct cmp_max_used_bits max_used_bits = cmp_get_max_used_bits(); + + for (i = 0; i < samples; i++) { + data[i].exp_flags = random_between(0, set_n_bits(max_used_bits.s_exp_flags)); + data[i].fx = random_between(0, set_n_bits(max_used_bits.s_fx)); + data[i].efx = random_between(0, set_n_bits(max_used_bits.s_efx)); + } +} + + +static void gen_s_fx_ncob_data(struct s_fx_ncob *data, uint32_t samples) +{ + uint32_t i; + struct cmp_max_used_bits max_used_bits = cmp_get_max_used_bits(); + + for (i = 0; i < samples; i++) { + data[i].exp_flags = random_between(0, set_n_bits(max_used_bits.s_exp_flags)); + data[i].fx = random_between(0, set_n_bits(max_used_bits.s_fx)); + data[i].ncob_x = random_between(0, set_n_bits(max_used_bits.s_ncob)); + data[i].ncob_y = random_between(0, set_n_bits(max_used_bits.s_ncob)); + } +} + + +static void gen_s_fx_efx_ncob_ecob_data(struct s_fx_efx_ncob_ecob *data, uint32_t samples) +{ + uint32_t i; + struct cmp_max_used_bits max_used_bits = cmp_get_max_used_bits(); + + for (i = 0; i < samples; i++) { + data[i].exp_flags = random_between(0, set_n_bits(max_used_bits.s_exp_flags)); + data[i].fx = random_between(0, set_n_bits(max_used_bits.s_fx)); + data[i].ncob_x = random_between(0, set_n_bits(max_used_bits.s_ncob)); + data[i].ncob_y = random_between(0, set_n_bits(max_used_bits.s_ncob)); + data[i].efx = random_between(0, set_n_bits(max_used_bits.s_efx)); + data[i].ecob_x = random_between(0, set_n_bits(max_used_bits.s_ecob)); + data[i].ecob_y = random_between(0, set_n_bits(max_used_bits.s_ecob)); + } +} + + +static void gen_f_fx_data(struct f_fx *data, uint32_t samples) +{ + uint32_t i; + struct cmp_max_used_bits max_used_bits = cmp_get_max_used_bits(); + + for (i = 0; i < samples; i++) { + data[i].fx = random_between(0, set_n_bits(max_used_bits.f_fx)); + } +} + + +static void gen_f_fx_efx_data(struct f_fx_efx *data, uint32_t samples) +{ + uint32_t i; + struct cmp_max_used_bits max_used_bits = cmp_get_max_used_bits(); + + for (i = 0; i < samples; i++) { + data[i].fx = random_between(0, set_n_bits(max_used_bits.f_fx)); + data[i].efx = random_between(0, set_n_bits(max_used_bits.f_efx)); + } +} + + +static void gen_f_fx_ncob_data(struct f_fx_ncob *data, uint32_t samples) +{ + uint32_t i; + struct cmp_max_used_bits max_used_bits = cmp_get_max_used_bits(); + + for (i = 0; i < samples; i++) { + data[i].fx = random_between(0, set_n_bits(max_used_bits.f_fx)); + data[i].ncob_x = random_between(0, set_n_bits(max_used_bits.f_ncob)); + data[i].ncob_y = random_between(0, set_n_bits(max_used_bits.f_ncob)); + } +} + + +static void gen_f_fx_efx_ncob_ecob_data(struct f_fx_efx_ncob_ecob *data, uint32_t samples) +{ + uint32_t i; + struct cmp_max_used_bits max_used_bits = cmp_get_max_used_bits(); + + for (i = 0; i < samples; i++) { + data[i].fx = random_between(0, set_n_bits(max_used_bits.f_fx)); + data[i].ncob_x = random_between(0, set_n_bits(max_used_bits.f_ncob)); + data[i].ncob_y = random_between(0, set_n_bits(max_used_bits.f_ncob)); + data[i].efx = random_between(0, set_n_bits(max_used_bits.f_efx)); + data[i].ecob_x = random_between(0, set_n_bits(max_used_bits.f_ecob)); + data[i].ecob_y = random_between(0, set_n_bits(max_used_bits.f_ecob)); + } +} + + +static void gen_l_fx_data(struct l_fx *data, uint32_t samples) +{ + uint32_t i; + struct cmp_max_used_bits max_used_bits = cmp_get_max_used_bits(); + + for (i = 0; i < samples; i++) { + data[i].exp_flags = random_between(0, set_n_bits(max_used_bits.l_exp_flags)); + data[i].fx = random_between(0, set_n_bits(max_used_bits.l_fx)); + data[i].fx_variance = random_between(0, set_n_bits(max_used_bits.l_fx_variance)); + } +} + + +static void gen_l_fx_efx_data(struct l_fx_efx *data, uint32_t samples) +{ + uint32_t i; + struct cmp_max_used_bits max_used_bits = cmp_get_max_used_bits(); + + for (i = 0; i < samples; i++) { + data[i].exp_flags = random_between(0, set_n_bits(max_used_bits.l_exp_flags)); + data[i].fx = random_between(0, set_n_bits(max_used_bits.l_fx)); + data[i].efx = random_between(0, set_n_bits(max_used_bits.l_efx)); + data[i].fx_variance = random_between(0, set_n_bits(max_used_bits.l_fx_variance)); + } +} + + +static void gen_l_fx_ncob_data(struct l_fx_ncob *data, uint32_t samples) +{ + uint32_t i; + struct cmp_max_used_bits max_used_bits = cmp_get_max_used_bits(); + + for (i = 0; i < samples; i++) { + data[i].exp_flags = random_between(0, set_n_bits(max_used_bits.l_exp_flags)); + data[i].fx = random_between(0, set_n_bits(max_used_bits.l_fx)); + data[i].ncob_x = random_between(0, set_n_bits(max_used_bits.l_ncob)); + data[i].ncob_y = random_between(0, set_n_bits(max_used_bits.l_ncob)); + data[i].fx_variance = random_between(0, set_n_bits(max_used_bits.l_fx_variance)); + data[i].cob_x_variance = random_between(0, set_n_bits(max_used_bits.l_cob_variance)); + data[i].cob_y_variance = random_between(0, set_n_bits(max_used_bits.l_cob_variance)); + } +} + + +static void gen_l_fx_efx_ncob_ecob_data(struct l_fx_efx_ncob_ecob *data, uint32_t samples) +{ + uint32_t i; + struct cmp_max_used_bits max_used_bits = cmp_get_max_used_bits(); + + for (i = 0; i < samples; i++) { + data[i].exp_flags = random_between(0, set_n_bits(max_used_bits.l_exp_flags)); + data[i].fx = random_between(0, set_n_bits(max_used_bits.l_fx)); + data[i].ncob_x = random_between(0, set_n_bits(max_used_bits.l_ncob)); + data[i].ncob_y = random_between(0, set_n_bits(max_used_bits.l_ncob)); + data[i].efx = random_between(0, set_n_bits(max_used_bits.l_efx)); + data[i].ecob_x = random_between(0, set_n_bits(max_used_bits.l_ecob)); + data[i].ecob_y = random_between(0, set_n_bits(max_used_bits.l_ecob)); + data[i].fx_variance = random_between(0, set_n_bits(max_used_bits.l_fx_variance)); + data[i].cob_x_variance = random_between(0, set_n_bits(max_used_bits.l_cob_variance)); + data[i].cob_y_variance = random_between(0, set_n_bits(max_used_bits.l_cob_variance)); + } +} + + +/** + * @brief generate random test data + * + * @param samples number of random test samples + * @param data_type compression data type of the test data + * + * @returns a pointer to the generated random test data + */ + +void *generate_random_test_data(uint32_t samples, enum cmp_data_type data_type) +{ + size_t data_size = cmp_cal_size_of_data(samples, data_type); + void *data = malloc(data_size); + void *data_cpy = data; + uint8_t *p = data; + + TEST_ASSERT_NOT_EQUAL_INT(data_size, 0); + TEST_ASSERT(data_size < (CMP_ENTITY_MAX_SIZE - NON_IMAGETTE_HEADER_SIZE)); + TEST_ASSERT_NOT_NULL(data); + + if (!rdcu_supported_data_type_is_used(data_type)) { + int i; + TEST_ASSERT(data_size > MULTI_ENTRY_HDR_SIZE); + for (i = 0; i < MULTI_ENTRY_HDR_SIZE; ++i) { + *p++ = random_between(0, UINT8_MAX); + } + data = p; + } + + switch (data_type) { + case DATA_TYPE_IMAGETTE: + case DATA_TYPE_IMAGETTE_ADAPTIVE: + case DATA_TYPE_SAT_IMAGETTE: + case DATA_TYPE_SAT_IMAGETTE_ADAPTIVE: + case DATA_TYPE_F_CAM_IMAGETTE: + case DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE: + gen_ima_data(data, samples); + break; + case DATA_TYPE_OFFSET: + gen_offset_data(data, samples); + break; + case DATA_TYPE_BACKGROUND: + gen_background_data(data, samples); + break; + case DATA_TYPE_SMEARING: + gen_smearing_data(data, samples); + break; + case DATA_TYPE_S_FX: + gen_s_fx_data(data, samples); + break; + case DATA_TYPE_S_FX_EFX: + gen_s_fx_efx_data(data, samples); + break; + case DATA_TYPE_S_FX_NCOB: + gen_s_fx_ncob_data(data, samples); + break; + case DATA_TYPE_S_FX_EFX_NCOB_ECOB: + gen_s_fx_efx_ncob_ecob_data(data, samples); + break; + case DATA_TYPE_L_FX: + gen_l_fx_data(data, samples); + break; + case DATA_TYPE_L_FX_EFX: + gen_l_fx_efx_data(data, samples); + break; + case DATA_TYPE_L_FX_NCOB: + gen_l_fx_ncob_data(data, samples); + break; + case DATA_TYPE_L_FX_EFX_NCOB_ECOB: + gen_l_fx_efx_ncob_ecob_data(data, samples); + break; + case DATA_TYPE_F_FX: + gen_f_fx_data(data, samples); + break; + case DATA_TYPE_F_FX_EFX: + gen_f_fx_efx_data(data, samples); + break; + case DATA_TYPE_F_FX_NCOB: + gen_f_fx_ncob_data(data, samples); + break; + case DATA_TYPE_F_FX_EFX_NCOB_ECOB: + gen_f_fx_efx_ncob_ecob_data(data, samples); + break; + case DATA_TYPE_F_CAM_OFFSET: /* TODO: implement this */ + case DATA_TYPE_F_CAM_BACKGROUND: /* TODO: implement this */ + default: + TEST_FAIL(); + } + + return data_cpy; +} + + +/** + * @brief generate random compression configuration + * + * @param cfg pointer to a compression configuration + * + */ + +void generate_random_cmp_par(struct cmp_cfg *cfg) +{ + cfg->golomb_par = random_between(MIN_IMA_GOLOMB_PAR, MAX_IMA_GOLOMB_PAR); + cfg->ap1_golomb_par = random_between(MIN_IMA_GOLOMB_PAR, MAX_IMA_GOLOMB_PAR); + cfg->ap2_golomb_par = random_between(MIN_IMA_GOLOMB_PAR, MAX_IMA_GOLOMB_PAR); + + cfg->cmp_par_exp_flags = random_between(MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); + cfg->cmp_par_fx = random_between(MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); + cfg->cmp_par_ncob = random_between(MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); + cfg->cmp_par_efx = random_between(MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); + cfg->cmp_par_ecob = random_between(MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); + cfg->cmp_par_fx_cob_variance = random_between(MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); + cfg->cmp_par_mean = random_between(MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); + cfg->cmp_par_variance = random_between(MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); + cfg->cmp_par_pixels_error = random_between(MIN_ICU_GOLOMB_PAR, MAX_ICU_GOLOMB_PAR); + + + cfg->spill = random_between(MIN_IMA_SPILL, cmp_ima_max_spill(cfg->golomb_par)); + cfg->ap1_spill = random_between(MIN_IMA_SPILL, cmp_ima_max_spill(cfg->ap1_golomb_par)); + cfg->ap2_spill = random_between(MIN_IMA_SPILL, cmp_ima_max_spill(cfg->ap2_golomb_par)); + + cfg->spill_exp_flags = random_between(MIN_ICU_SPILL, cmp_icu_max_spill(cfg->cmp_par_exp_flags)); + cfg->spill_fx = random_between(MIN_ICU_SPILL, cmp_icu_max_spill(cfg->cmp_par_fx)); + cfg->spill_ncob = random_between(MIN_ICU_SPILL, cmp_icu_max_spill(cfg->cmp_par_ncob)); + cfg->spill_efx = random_between(MIN_ICU_SPILL, cmp_icu_max_spill(cfg->cmp_par_efx)); + cfg->spill_ecob = random_between(MIN_ICU_SPILL, cmp_icu_max_spill(cfg->cmp_par_ecob)); + cfg->spill_fx_cob_variance = random_between(MIN_ICU_SPILL, cmp_icu_max_spill(cfg->cmp_par_fx_cob_variance)); + cfg->spill_mean = random_between(MIN_ICU_SPILL, cmp_icu_max_spill(cfg->cmp_par_mean)); + cfg->spill_variance = random_between(MIN_ICU_SPILL, cmp_icu_max_spill(cfg->cmp_par_variance)); + cfg->spill_pixels_error = random_between(MIN_ICU_SPILL, cmp_icu_max_spill(cfg->cmp_par_pixels_error)); +#if 0 + if (cfg->cmp_mode == CMP_MODE_STUFF) { + /* cfg->golomb_par = random_between(16, MAX_STUFF_CMP_PAR); */ + cfg->golomb_par = 16; + cfg->ap1_golomb_par = random_between(0, MAX_STUFF_CMP_PAR); + cfg->ap2_golomb_par = random_between(0, MAX_STUFF_CMP_PAR); + cfg->cmp_par_exp_flags = random_between(0, MAX_STUFF_CMP_PAR); + cfg->cmp_par_fx = random_between(0, MAX_STUFF_CMP_PAR); + cfg->cmp_par_ncob = random_between(0, MAX_STUFF_CMP_PAR); + cfg->cmp_par_efx = random_between(0, MAX_STUFF_CMP_PAR); + cfg->cmp_par_ecob = random_between(0, MAX_STUFF_CMP_PAR); + cfg->cmp_par_fx_cob_variance = random_between(0, MAX_STUFF_CMP_PAR); + cfg->cmp_par_mean = random_between(0, MAX_STUFF_CMP_PAR); + cfg->cmp_par_variance = random_between(0, MAX_STUFF_CMP_PAR); + cfg->cmp_par_pixels_error = random_between(0, MAX_STUFF_CMP_PAR); + return; + } +#endif +} + + +/** + * @brief compress the given configuration and decompress it afterwards; finally + * compare the results + * + * @param cfg pointer to a compression configuration + */ + +void compression_decompression(struct cmp_cfg *cfg) +{ + int cmp_size_bits, s, error; + int data_size, cmp_data_size; + struct cmp_entity *ent; + void *decompressed_data; + static void *model_of_data; + void *updated_model = NULL; + + TEST_ASSERT_NOT_NULL(cfg); + + TEST_ASSERT_NULL(cfg->icu_output_buf); + + data_size = cmp_cal_size_of_data(cfg->samples, cfg->data_type); + + /* create a compression entity */ + cmp_data_size = cmp_cal_size_of_data(cfg->buffer_length, cfg->data_type); + cmp_data_size &= ~0x3; /* the size of the compressed data should be a multiple of 4 */ + TEST_ASSERT_NOT_EQUAL_INT(0, cmp_data_size); + + s = cmp_ent_create(NULL, cfg->data_type, cfg->cmp_mode == CMP_MODE_RAW, cmp_data_size); + TEST_ASSERT_NOT_EQUAL_INT(0, s); + ent = malloc(s); TEST_ASSERT_TRUE(ent); + s = cmp_ent_create(ent, cfg->data_type, cfg->cmp_mode == CMP_MODE_RAW, cmp_data_size); + TEST_ASSERT_NOT_EQUAL_INT(0, s); + + /* we put the coompressed data direct into the compression entity */ + cfg->icu_output_buf = cmp_ent_get_data_buf(ent); + TEST_ASSERT_NOT_NULL(cfg->icu_output_buf); + + /* now compress the data */ + cmp_size_bits = icu_compress_data(cfg); + TEST_ASSERT(cmp_size_bits > 0); + + /* put the compression parameters in the entity header */ + error = cmp_ent_write_cmp_pars(ent, cfg, cmp_size_bits); + TEST_ASSERT_FALSE(error); + + /* allocate the buffers for decompression */ + TEST_ASSERT_NOT_EQUAL_INT(0, data_size); + s = decompress_cmp_entiy(ent, model_of_data, NULL, NULL); + decompressed_data = malloc(s); TEST_ASSERT_NOT_NULL(decompressed_data); + + if (model_mode_is_used(cfg->cmp_mode)) { + updated_model = malloc(data_size); + TEST_ASSERT_NOT_NULL(updated_model); + } + + /* now we try to decompress the data */ + s = decompress_cmp_entiy(ent, model_of_data, updated_model, decompressed_data); + TEST_ASSERT_EQUAL_INT(data_size, s); + TEST_ASSERT_FALSE(memcmp(decompressed_data, cfg->input_buf, data_size)); + + if (model_mode_is_used(cfg->cmp_mode)) { + TEST_ASSERT_NOT_NULL(updated_model); + TEST_ASSERT_NOT_NULL(model_of_data); + TEST_ASSERT_FALSE(memcmp(updated_model, cfg->icu_new_model_buf, data_size)); + memcpy(model_of_data, updated_model, data_size); + } else { /* non-model mode */ + /* reset model */ + free(model_of_data); + model_of_data = malloc(data_size); + memcpy(model_of_data, decompressed_data, data_size); + } + + cfg->icu_output_buf = NULL; + free(ent); + free(decompressed_data); + free(updated_model); +} + + +#define CMP_BUFFER_FAKTOR 3 /* compression data buffer size / data to compress buffer size */ + +/** + * @brief random compression decompression test + * @detail We generate random data and compress them with random parameters. + * After that we put the data in a compression entity. We decompress the + * compression entity and compare the decompressed data with the original + * data. + * @test icu_compress_data + * @test decompress_cmp_entiy + */ + +void test_random_compression_decompression(void) +{ + enum cmp_data_type data_type; + enum cmp_mode cmp_mode; + struct cmp_cfg cfg; + int cmp_buffer_size; + + /* TODO: extend test for DATA_TYPE_F_CAM_BACKGROUND, DATA_TYPE_F_CAM_OFFSET */ + for (data_type = 1; data_type <= DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE; data_type++) { + /* printf("%s\n", data_type2string(data_type)); */ + /* generate random data*/ + uint32_t samples = random_between(1, 430179/CMP_BUFFER_FAKTOR); + uint32_t model_value = random_between(0, MAX_MODEL_VALUE); + void *data_to_compress1 = generate_random_test_data(samples, data_type); + void *data_to_compress2 = generate_random_test_data(samples, data_type); + void *updated_model = calloc(1, cmp_cal_size_of_data(samples, data_type)); + /* for (cmp_mode = CMP_MODE_RAW; cmp_mode <= CMP_MODE_STUFF; cmp_mode++) { */ + for (cmp_mode = CMP_MODE_RAW; cmp_mode < CMP_MODE_STUFF; cmp_mode++) { + /* printf("cmp_mode: %i\n", cmp_mode); */ + cfg = cmp_cfg_icu_create(data_type, cmp_mode, model_value, + CMP_LOSSLESS); + TEST_ASSERT_NOT_EQUAL_INT(cfg.data_type, DATA_TYPE_UNKNOWN); + + generate_random_cmp_par(&cfg); + + if (!model_mode_is_used(cmp_mode)) + cmp_buffer_size = cmp_cfg_icu_buffers(&cfg, data_to_compress1, + samples, NULL, NULL, NULL, samples*CMP_BUFFER_FAKTOR); + else + cmp_buffer_size = cmp_cfg_icu_buffers(&cfg, data_to_compress2, + samples, data_to_compress1, updated_model, NULL, samples*CMP_BUFFER_FAKTOR); + + TEST_ASSERT_EQUAL_INT(cmp_buffer_size, cmp_cal_size_of_data(CMP_BUFFER_FAKTOR*samples, data_type)); + + compression_decompression(&cfg); + } + free(data_to_compress1); + free(data_to_compress2); + free(updated_model); + } +} diff --git a/test/meson.build b/test/meson.build index 145cdb8..11ddac0 100644 --- a/test/meson.build +++ b/test/meson.build @@ -38,3 +38,4 @@ unity_dep = dependency('unity', fallback : ['unity', 'unity_dep']) subdir('cmp_icu') subdir('cmp_data_types') +subdir('cmp_entity') -- GitLab From 7690fa65a4c6cc77db7d218d78ba9dfe84e9029e Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Tue, 27 Sep 2022 11:25:24 +0200 Subject: [PATCH 39/46] now the adaptive compression size (ap1_cmp_size, ap2_cmp_size) is calculate when the --rdcu_par option is used --- cmp_tool.c | 74 +++++++++++++--------- test/cmp_tool/cmp_tool_integration_test.py | 4 +- 2 files changed, 45 insertions(+), 33 deletions(-) diff --git a/cmp_tool.c b/cmp_tool.c index 86302b3..3572c23 100644 --- a/cmp_tool.c +++ b/cmp_tool.c @@ -90,7 +90,7 @@ static const char *output_prefix = DEFAULT_OUTPUT_PREFIX; /* if non zero additional RDCU parameters are included in the compression * configuration and decompression information files */ -static int print_rdcu_cfg; +static int add_rdcu_pars; /* if non zero generate RDCU setup packets */ static int rdcu_pkt_mode; @@ -160,7 +160,7 @@ int main(int argc, char **argv) NULL)) != -1) { switch (opt) { case 'a': /* --rdcu_par */ - print_rdcu_cfg = 1; + add_rdcu_pars = 1; break; case 'c': cmp_operation = 1; @@ -271,7 +271,7 @@ int main(int argc, char **argv) CMP_DEF_IMA_MODEL_GOLOMB_PAR, CMP_DEF_IMA_MODEL_SPILL_PAR, CMP_DEF_IMA_MODEL_AP1_GOLOMB_PAR, CMP_DEF_IMA_MODEL_AP1_SPILL_PAR, CMP_DEF_IMA_MODEL_AP2_GOLOMB_PAR, CMP_DEF_IMA_MODEL_AP2_SPILL_PAR); - print_cfg(&cfg, print_rdcu_cfg); + print_cfg(&cfg, add_rdcu_pars); exit(EXIT_SUCCESS); } @@ -285,7 +285,7 @@ int main(int argc, char **argv) CMP_DEF_IMA_DIFF_GOLOMB_PAR, CMP_DEF_IMA_DIFF_SPILL_PAR, CMP_DEF_IMA_DIFF_AP1_GOLOMB_PAR, CMP_DEF_IMA_DIFF_AP1_SPILL_PAR, CMP_DEF_IMA_DIFF_AP2_GOLOMB_PAR, CMP_DEF_IMA_DIFF_AP2_SPILL_PAR); - print_cfg(&cfg, print_rdcu_cfg); + print_cfg(&cfg, add_rdcu_pars); exit(EXIT_SUCCESS); } @@ -369,7 +369,7 @@ int main(int argc, char **argv) ent_size = cmp_ent_create(NULL, DATA_TYPE_IMAGETTE, info.cmp_mode_used == CMP_MODE_RAW, cmp_size_byte); - if (ent_size <= 0) + if (!ent_size) goto fail; decomp_entity = malloc(ent_size); if (!decomp_entity) { @@ -378,7 +378,7 @@ int main(int argc, char **argv) } ent_size = cmp_ent_create(decomp_entity, DATA_TYPE_IMAGETTE, info.cmp_mode_used == CMP_MODE_RAW, cmp_size_byte); - if (ent_size <= 0) + if (!ent_size) goto fail; if (info.cmp_mode_used == CMP_MODE_RAW) @@ -531,7 +531,7 @@ static int guess_cmp_pars(struct cmp_cfg *cfg, const char *guess_cmp_mode, printf("Search for a good set of compression parameters (level: %d) ... ", guess_level); if (!strcmp(guess_cmp_mode, "RDCU")) { - if (print_rdcu_cfg) + if (add_rdcu_pars) cfg->data_type = DATA_TYPE_IMAGETTE_ADAPTIVE; else cfg->data_type = DATA_TYPE_IMAGETTE; @@ -563,7 +563,7 @@ static int guess_cmp_pars(struct cmp_cfg *cfg, const char *guess_cmp_mode, printf("DONE\n"); printf("Write the guessed compression configuration to file %s.cfg ... ", output_prefix); - error = write_cfg(cfg, output_prefix, print_rdcu_cfg, verbose_en); + error = write_cfg(cfg, output_prefix, add_rdcu_pars, verbose_en); if (error) return -1; printf("DONE\n"); @@ -624,10 +624,11 @@ static int gen_rdcu_write_pkts(struct cmp_cfg *cfg) * @note set cmp_size, ap1_cmp_size, ap2_cmp_size will be set to 0 * * @returns 0 on success, error otherwise - * TODO: set cmp_err in error case + * TODO: set cmp_mode_err, set model_value_err, etc, in error case */ -static int cmp_gernate_rdcu_info(const struct cmp_cfg *cfg, int cmp_size_bit, struct cmp_info *info) +static int cmp_gernate_rdcu_info(const struct cmp_cfg *cfg, int cmp_size_bit, + struct cmp_info *info) { if (!cfg) return -1; @@ -649,21 +650,39 @@ static int cmp_gernate_rdcu_info(const struct cmp_cfg *cfg, int cmp_size_bit, st info->spill_used = cfg->spill; info->golomb_par_used = cfg->golomb_par; info->samples_used = cfg->samples; - info->cmp_size = 0; - info->ap1_cmp_size = 0; - info->ap2_cmp_size = 0; info->rdcu_new_model_adr_used = cfg->rdcu_new_model_adr; info->rdcu_cmp_adr_used = cfg->rdcu_buffer_adr; if (cmp_size_bit == CMP_ERROR_SMALL_BUF) /* the icu_output_buf is to small to store the whole bitstream */ info->cmp_err |= 1UL << SMALL_BUFFER_ERR_BIT; /* set small buffer error */ - if (cmp_size_bit < 0) + if (cmp_size_bit < 0) { info->cmp_size = 0; - else + info->ap1_cmp_size = 0; + info->ap2_cmp_size = 0; + } else { info->cmp_size = (uint32_t)cmp_size_bit; - } + if (add_rdcu_pars) { + struct cmp_cfg cfg_cpy = *cfg; + + cfg_cpy.icu_output_buf = NULL; + cfg_cpy.icu_new_model_buf = NULL; + + cfg_cpy.golomb_par = cfg_cpy.ap1_golomb_par; + cfg_cpy.spill = cfg_cpy.ap1_spill; + info->ap1_cmp_size = icu_compress_data(&cfg_cpy); + if ((int)info->ap1_cmp_size < 0) + info->ap1_cmp_size = 0; + + cfg_cpy.golomb_par = cfg_cpy.ap2_golomb_par; + cfg_cpy.spill = cfg_cpy.ap2_spill; + info->ap2_cmp_size = icu_compress_data(&cfg_cpy); + if ((int)info->ap2_cmp_size < 0) + info->ap2_cmp_size = 0; + } + } + } return 0; } @@ -711,15 +730,8 @@ static int compression(struct cmp_cfg *cfg, struct cmp_info *info) cfg->icu_output_buf = cmp_ent_get_data_buf(cmp_entity); cmp_size = icu_compress_data(cfg); - cmp_gernate_rdcu_info(cfg, cmp_size, info); - if (cmp_size < 0 || info->cmp_err != 0) { - if (info->cmp_err) - printf("\nCompression error 0x%02X\n... ", info->cmp_err); - /* TODO: add a parse cmp error function */ - /* if ((info->cmp_err >> SMALL_BUFFER_ERR_BIT) & 1U) */ - /* fprintf(stderr, "%s: the buffer for the compressed data is too small. Try a larger buffer_length parameter.\n", PROGRAM_NAME); */ + if (cmp_size < 0) goto error_cleanup; - } if (model_id_str) { uint32_t red_val; @@ -750,6 +762,8 @@ static int compression(struct cmp_cfg *cfg, struct cmp_info *info) data_to_write_to_file = cmp_entity; cmp_size_byte = cmp_ent_get_size(cmp_entity); } else { + if (cmp_gernate_rdcu_info(cfg, cmp_size, info)) + goto error_cleanup; data_to_write_to_file = cmp_ent_get_data_buf(cmp_entity); if (cfg->cmp_mode == CMP_MODE_RAW) cmp_size_byte = info->cmp_size/CHAR_BIT; @@ -777,16 +791,16 @@ static int compression(struct cmp_cfg *cfg, struct cmp_info *info) if (!include_cmp_header) { printf("Write decompression information to file %s.info ... ", output_prefix); - error = write_info(info, output_prefix, print_rdcu_cfg); + error = write_info(info, output_prefix, add_rdcu_pars); if (error) goto error_cleanup; printf("DONE\n"); - } - if (verbose_en) { - printf("\n"); - print_cmp_info(info); - printf("\n"); + if (verbose_en) { + printf("\n"); + print_cmp_info(info); + printf("\n"); + } } free(cmp_entity); diff --git a/test/cmp_tool/cmp_tool_integration_test.py b/test/cmp_tool/cmp_tool_integration_test.py index f08d53b..7d58943 100755 --- a/test/cmp_tool/cmp_tool_integration_test.py +++ b/test/cmp_tool/cmp_tool_integration_test.py @@ -831,9 +831,7 @@ def test_small_buf_err(): assert(stdout == CMP_START_STR_CMP + "Importing configuration file %s ... DONE\n" % (cfg_file_name) + "Importing data file %s ... DONE\n" % (data_file_name) + - "Compress data ... \n" - "Compression error 0x01\n" - "... FAILED\n") + "Compress data ... FAILED\n") # assert(stderr == "cmp_tool: the buffer for the compressed data is too small. Try a larger buffer_length parameter.\n") assert(stderr == "Error: The buffer for the compressed data is too small to hold the compressed data. Try a larger buffer_length parameter.\n") assert(returncode == EXIT_FAILURE) -- GitLab From 6cd59968613e183b70644fd8da714ede238e66f3 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Tue, 27 Sep 2022 16:39:27 +0200 Subject: [PATCH 40/46] Refactor code; add documentation --- include/decmp.h | 9 +- lib/decmp.c | 242 +++++++++++++++++++++++--------------- test/cmp_icu/test_decmp.c | 83 +++++++++---- 3 files changed, 214 insertions(+), 120 deletions(-) diff --git a/include/decmp.h b/include/decmp.h index 4046798..826eb89 100644 --- a/include/decmp.h +++ b/include/decmp.h @@ -22,10 +22,11 @@ #include <cmp_entity.h> #include <cmp_support.h> -int decompress_data(uint32_t *compressed_data, void *de_model_buf, - const struct cmp_info *info, void *decompressed_data); - -int decompress_cmp_entiy(struct cmp_entity *ent, void *model_buf, +int decompress_cmp_entiy(struct cmp_entity *ent, void *model_of_data, void *up_model_buf, void *decompressed_data); +int decompress_rdcu_data(uint32_t *compressed_data, const struct cmp_info *info, + uint16_t *model_of_data, uint16_t *up_model_buf, + uint16_t *decompressed_data); + #endif /* DECMP_H_ */ diff --git a/lib/decmp.c b/lib/decmp.c index c266e64..7bea1e1 100644 --- a/lib/decmp.c +++ b/lib/decmp.c @@ -16,7 +16,8 @@ * @brief software decompression library * @see Data Compression User Manual PLATO-UVIE-PL-UM-0001 * - * TODO: how to decomprss? + * To decompress a compression entity (consisting of a compression entity header + * and the compressed data) use the decompress_cmp_entiy() function. */ #include <stdint.h> @@ -34,6 +35,7 @@ #define MAX_CW_LEN 32 /* maximum Golomb code word bit length */ + /* maximum used bits registry */ extern struct cmp_max_used_bits max_used_bits; @@ -59,7 +61,7 @@ struct decoder_setup { /** * @brief count leading 1-bits * - * @param value input vale + * @param value input vale to count * * @returns the number of leading 1-bits in value, starting at the most * significant bit position @@ -67,18 +69,10 @@ struct decoder_setup { static unsigned int count_leading_ones(uint32_t value) { - unsigned int n_ones = 0; /* number of leading 1s */ - - while (1) { - uint32_t leading_bit = value & 0x80000000; + if (value == 0xFFFFFFFF) + return 32; - if (!leading_bit) - break; - - n_ones++; - value <<= 1; - } - return n_ones; + return __builtin_clz(~value); } @@ -100,19 +94,17 @@ static int rice_decoder(uint32_t code_word, unsigned int m, unsigned int log2_m, unsigned int q; /* quotient code */ unsigned int ql; /* length of the quotient code */ unsigned int r; /* remainder code */ - unsigned int rl; /* length of the remainder code */ + unsigned int rl = log2_m; /* length of the remainder code */ unsigned int cw_len; /* length of the decoded code word in bits */ (void)m; /* we don't need the Golomb parameter */ - if (log2_m > 32) + if (log2_m > 32) /* because m has 32 bits log2_m can not be bigger than 32 */ return 0; q = count_leading_ones(code_word); /* decode unary coding */ ql = q + 1; /* Number of 1's + following 0 */ - rl = log2_m; - cw_len = rl + ql; if (cw_len > 32) /* can only decode code words with maximum 32 bits */ @@ -121,8 +113,7 @@ static int rice_decoder(uint32_t code_word, unsigned int m, unsigned int log2_m, code_word = code_word << ql; /* shift quotient code out */ /* Right shifting an integer by a number of bits equal or greater than - * its size is undefined behavior - */ + * its size is undefined behavior */ if (rl == 0) r = 0; else @@ -308,7 +299,7 @@ static int decode_normal(uint32_t *decoded_value, int stream_pos, unsigned int n_read_bits; int stream_pos_read, cw_len; - /* check if we can read max_cw_len or less; we do not now how long the + /* check if we can read max_cw_len or less; we do not know how long the * code word actually is so we try to read the maximum cw length */ if ((unsigned int)stream_pos + 32 > setup->max_stream_len) n_read_bits = setup->max_stream_len - (unsigned int)stream_pos; @@ -327,7 +318,7 @@ static int decode_normal(uint32_t *decoded_value, int stream_pos, setup->encoder_par2, decoded_value); if (cw_len <= 0) return -1; - /* consistency check: code word length can not be bigger than read bits */ + /* consistency check: code word length can not be bigger than the read bits */ if (cw_len > (int)n_read_bits) return -1; @@ -400,6 +391,8 @@ static int decode_multi(uint32_t *decoded_value, int stream_pos, const struct decoder_setup *setup) { stream_pos = decode_normal(decoded_value, stream_pos, setup); + if (stream_pos < 0) + return stream_pos; if (*decoded_value >= setup->outlier_par) { /* escape symbol mechanism was used; read unencoded value */ @@ -442,7 +435,7 @@ static int decode_none(uint32_t *decoded_value, int stream_pos, /** - * @brief remap a unsigned value back to a signed value + * @brief remap an unsigned value back to a signed value * @note this is the reverse function of map_to_pos() * * @param value_to_unmap unsigned value to remap @@ -481,27 +474,46 @@ static int decode_value(uint32_t *decoded_value, uint32_t model, { uint32_t mask = (~0U >> (32 - setup->max_data_bits)); /* mask the used bits */ + /* decode the next value from the bitstream */ stream_pos = setup->decode_method_f(decoded_value, stream_pos, setup); if (stream_pos <= 0) return stream_pos; - if (setup->decode_method_f == decode_none) /* we are done here in stuff mode */ + if (setup->decode_method_f == decode_none) + /* we are done here in stuff mode */ return stream_pos; + /* map the unsigned decode value back to a signed value */ *decoded_value = re_map_to_pos(*decoded_value); + /* decorate data the data with the model */ *decoded_value += round_fwd(model, setup->lossy_par); - *decoded_value &= mask; /* TODO: why?? */ + /* we mask only the used bits in case there is an overflow when adding the model */ + *decoded_value &= mask; + /* inverse step of the lossy compression */ *decoded_value = round_inv(*decoded_value, setup->lossy_par); return stream_pos; } +/** + * @brief configure a decoder setup structure to have a setup to decode a vale + * + * @param setup pointer to the decoder setup + * @param cmp_par compression parameter + * @param spillover spillover_par parameter + * @param lossy_par lossy compression parameter + * @param max_data_bits how many bits are needed to represent the highest possible value + * @param cfg pointer to the compression configuration structure + * + * @returns 0 on success; otherwise error + */ + static int configure_decoder_setup(struct decoder_setup *setup, - uint32_t cmp_par, uint32_t spill, + uint32_t cmp_par, uint32_t spillover, uint32_t lossy_par, uint32_t max_data_bits, const struct cmp_cfg *cfg) { @@ -527,7 +539,7 @@ static int configure_decoder_setup(struct decoder_setup *setup, if (ilog_2(cmp_par) < 0) return -1; setup->encoder_par2 = ilog_2(cmp_par); /* encoding parameter 2 */ - setup->outlier_par = spill; /* outlier parameter */ + setup->outlier_par = spillover; /* outlier parameter */ setup->lossy_par = lossy_par; /* lossy compression parameter */ setup->model_value = cfg->model_value; /* model value parameter */ setup->max_data_bits = max_data_bits; /* how many bits are needed to represent the highest possible value */ @@ -537,6 +549,16 @@ static int configure_decoder_setup(struct decoder_setup *setup, } +/** + * @brief decompress imagette data + * + * @param cfg pointer to the compression configuration structure + * + * @returns bit position of the last read bit in the bitstream on success; + * returns negative on error, returns CMP_ERROR_SMALL_BUF if the bitstream + * buffer is too small to read the value from the bitstream + */ + static int decompress_imagette(struct cmp_cfg *cfg) { int err; @@ -577,7 +599,7 @@ static int decompress_imagette(struct cmp_cfg *cfg) /** - * @brief TODO: decompress the multi-entry packet header structure and sets the data, + * @brief decompress the multi-entry packet header structure and sets the data, * model and up_model pointers to the data after the header * * @param data pointer to a pointer pointing to the data to be compressed @@ -585,7 +607,7 @@ static int decompress_imagette(struct cmp_cfg *cfg) * @param up_model pointer to a pointer pointing to the updated model buffer * @param cfg pointer to the compression configuration structure * - * @returns the bit length of the bitstream on success; negative on error, + * @returns the bit length of the bitstream on success * * @note the (void **) cast relies on all pointer types having the same internal * representation which is common, but not universal; http://www.c-faq.com/ptrs/genericpp.html @@ -624,9 +646,9 @@ static int decompress_multi_entry_hdr(void **data, void **model, void **up_model * * @param cfg pointer to the compression configuration structure * - * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the - * value in the bitstream + * @returns bit position of the last read bit in the bitstream on success; + * returns negative on error, returns CMP_ERROR_SMALL_BUF if the bitstream + * buffer is too small to read the value from the bitstream */ static int decompress_s_fx(const struct cmp_cfg *cfg) @@ -696,9 +718,9 @@ static int decompress_s_fx(const struct cmp_cfg *cfg) * * @param cfg pointer to the compression configuration structure * - * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the - * value in the bitstream + * @returns bit position of the last read bit in the bitstream on success; + * returns negative on error, returns CMP_ERROR_SMALL_BUF if the bitstream + * buffer is too small to read the value from the bitstream */ static int decompress_s_fx_efx(const struct cmp_cfg *cfg) @@ -779,9 +801,9 @@ static int decompress_s_fx_efx(const struct cmp_cfg *cfg) * * @param cfg pointer to the compression configuration structure * - * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the - * value in the bitstream + * @returns bit position of the last read bit in the bitstream on success; + * returns negative on error, returns CMP_ERROR_SMALL_BUF if the bitstream + * buffer is too small to read the value from the bitstream */ static int decompress_s_fx_ncob(const struct cmp_cfg *cfg) @@ -870,9 +892,9 @@ static int decompress_s_fx_ncob(const struct cmp_cfg *cfg) * * @param cfg pointer to the compression configuration structure * - * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the - * value in the bitstream + * @returns bit position of the last read bit in the bitstream on success; + * returns negative on error, returns CMP_ERROR_SMALL_BUF if the bitstream + * buffer is too small to read the value from the bitstream */ static int decompress_s_fx_efx_ncob_ecob(const struct cmp_cfg *cfg) @@ -991,9 +1013,9 @@ static int decompress_s_fx_efx_ncob_ecob(const struct cmp_cfg *cfg) * * @param cfg pointer to the compression configuration structure * - * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the - * value in the bitstream + * @returns bit position of the last read bit in the bitstream on success; + * returns negative on error, returns CMP_ERROR_SMALL_BUF if the bitstream + * buffer is too small to read the value from the bitstream */ static int decompress_f_fx(const struct cmp_cfg *cfg) @@ -1051,9 +1073,9 @@ static int decompress_f_fx(const struct cmp_cfg *cfg) * * @param cfg pointer to the compression configuration structure * - * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the - * value in the bitstream + * @returns bit position of the last read bit in the bitstream on success; + * returns negative on error, returns CMP_ERROR_SMALL_BUF if the bitstream + * buffer is too small to read the value from the bitstream */ static int decompress_f_fx_efx(const struct cmp_cfg *cfg) @@ -1123,9 +1145,9 @@ static int decompress_f_fx_efx(const struct cmp_cfg *cfg) * * @param cfg pointer to the compression configuration structure * - * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the - * value in the bitstream + * @returns bit position of the last read bit in the bitstream on success; + * returns negative on error, returns CMP_ERROR_SMALL_BUF if the bitstream + * buffer is too small to read the value from the bitstream */ static int decompress_f_fx_ncob(const struct cmp_cfg *cfg) @@ -1203,9 +1225,9 @@ static int decompress_f_fx_ncob(const struct cmp_cfg *cfg) * * @param cfg pointer to the compression configuration structure * - * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the - * value in the bitstream + * @returns bit position of the last read bit in the bitstream on success; + * returns negative on error, returns CMP_ERROR_SMALL_BUF if the bitstream + * buffer is too small to read the value from the bitstream */ static int decompress_f_fx_efx_ncob_ecob(const struct cmp_cfg *cfg) @@ -1313,9 +1335,9 @@ static int decompress_f_fx_efx_ncob_ecob(const struct cmp_cfg *cfg) * * @param cfg pointer to the compression configuration structure * - * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the - * value in the bitstream + * @returns bit position of the last read bit in the bitstream on success; + * returns negative on error, returns CMP_ERROR_SMALL_BUF if the bitstream + * buffer is too small to read the value from the bitstream */ static int decompress_l_fx(const struct cmp_cfg *cfg) @@ -1396,9 +1418,9 @@ static int decompress_l_fx(const struct cmp_cfg *cfg) * * @param cfg pointer to the compression configuration structure * - * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the - * value in the bitstream + * @returns bit position of the last read bit in the bitstream on success; + * returns negative on error, returns CMP_ERROR_SMALL_BUF if the bitstream + * buffer is too small to read the value from the bitstream */ static int decompress_l_fx_efx(const struct cmp_cfg *cfg) @@ -1490,9 +1512,9 @@ static int decompress_l_fx_efx(const struct cmp_cfg *cfg) * * @param cfg pointer to the compression configuration structure * - * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the - * value in the bitstream + * @returns bit position of the last read bit in the bitstream on success; + * returns negative on error, returns CMP_ERROR_SMALL_BUF if the bitstream + * buffer is too small to read the value from the bitstream */ static int decompress_l_fx_ncob(const struct cmp_cfg *cfg) @@ -1612,9 +1634,9 @@ static int decompress_l_fx_ncob(const struct cmp_cfg *cfg) * * @param cfg pointer to the compression configuration structure * - * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the - * value in the bitstream + * @returns bit position of the last read bit in the bitstream on success; + * returns negative on error, returns CMP_ERROR_SMALL_BUF if the bitstream + * buffer is too small to read the value from the bitstream */ static int decompress_l_fx_efx_ncob_ecob(const struct cmp_cfg *cfg) @@ -1764,9 +1786,9 @@ static int decompress_l_fx_efx_ncob_ecob(const struct cmp_cfg *cfg) * * @param cfg pointer to the compression configuration structure * - * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the - * value in the bitstream + * @returns bit position of the last read bit in the bitstream on success; + * returns negative on error, returns CMP_ERROR_SMALL_BUF if the bitstream + * buffer is too small to read the value from the bitstream */ static int decompress_nc_offset(const struct cmp_cfg *cfg) @@ -1836,9 +1858,9 @@ static int decompress_nc_offset(const struct cmp_cfg *cfg) * * @param cfg pointer to the compression configuration structure * - * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the - * value in the bitstream + * @returns bit position of the last read bit in the bitstream on success; + * returns negative on error, returns CMP_ERROR_SMALL_BUF if the bitstream + * buffer is too small to read the value from the bitstream */ static int decompress_nc_background(const struct cmp_cfg *cfg) @@ -1919,9 +1941,9 @@ static int decompress_nc_background(const struct cmp_cfg *cfg) * * @param cfg pointer to the compression configuration structure * - * @returns the bit length of the bitstream on success; negative on error, - * CMP_ERROR_SMALL_BUF if the bitstream buffer is too small to put the - * value in the bitstream + * @returns bit position of the last read bit in the bitstream on success; + * returns negative on error, returns CMP_ERROR_SMALL_BUF if the bitstream + * buffer is too small to read the value from the bitstream */ static int decompress_smearing(const struct cmp_cfg *cfg) @@ -1997,6 +2019,17 @@ static int decompress_smearing(const struct cmp_cfg *cfg) } +/** + * @brief decompress the data based on a compression configuration + * + * @param cfg pointer to a compression configuration + * + * @note cfg->buffer_length is measured in bytes (instead of samples as by the + * compression) + * + * @returns the size of the decompressed data on success; returns negative on failure + */ + static int decompressed_data_internal(struct cmp_cfg *cfg) { int data_size, strem_len_bit = -1; @@ -2094,9 +2127,7 @@ static int decompressed_data_internal(struct cmp_cfg *cfg) debug_print("Error: Compressed data type not supported.\n"); break; } - } - /* TODO: is this usefull? if (strem_len_bit != data_size * CHAR_BIT) { */ if (strem_len_bit <= 0) return -1; @@ -2104,13 +2135,27 @@ static int decompressed_data_internal(struct cmp_cfg *cfg) } -int decompress_cmp_entiy(struct cmp_entity *ent, void *model_buf, +/** + * @brief decompress a compression entity + * + * @param ent pointer to the compression entity to be decompressed + * @param model_of_data pointer to model data buffer (can be NULL if no + * model compression mode is used) + * @param updated_model pointer to store the updated model for the next model + * mode compression (can be the same as the model_of_data + * buffer for in-place update or NULL if updated model is not needed) + * @param decompressed_data pointer to the decompressed data buffer (can be NULL) + * + * @returns the size of the decompressed data on success; returns negative on failure + */ + +int decompress_cmp_entiy(struct cmp_entity *ent, void *model_of_data, void *up_model_buf, void *decompressed_data) { int err; struct cmp_cfg cfg = {0}; - cfg.model_buf = model_buf; + cfg.model_buf = model_of_data; cfg.icu_new_model_buf = up_model_buf; cfg.input_buf = decompressed_data; @@ -2125,12 +2170,26 @@ int decompress_cmp_entiy(struct cmp_entity *ent, void *model_buf, } -/* model buffer is overwritten with updated model */ +/** + * @brief decompress RDCU compressed data without a compression entity header + * + * @param compressed_data pointer to the RDCU compressed data (without a + * compression entity header) + * @param model_of_data pointer to model data buffer (can be NULL if no + * model compression mode is used) + * @param updated_model pointer to store the updated model for the next model + * mode compression (can be the same as the model_of_data + * buffer for in-place update or NULL if updated model is not needed) + * @param decompressed_data pointer to the decompressed data buffer (can be NULL) + * + * @returns the size of the decompressed data on success; returns negative on failure + */ + +int decompress_rdcu_data(uint32_t *compressed_data, const struct cmp_info *info, + uint16_t *model_of_data, uint16_t *up_model_buf, + uint16_t *decompressed_data) -int decompress_data(uint32_t *compressed_data, void *de_model_buf, - const struct cmp_info *info, void *decompressed_data) { - int size_decomp_data; struct cmp_cfg cfg = {0}; if (!compressed_data) @@ -2142,15 +2201,11 @@ int decompress_data(uint32_t *compressed_data, void *de_model_buf, if (info->cmp_err) return -1; - if (model_mode_is_used(info->cmp_mode_used)) - if (!de_model_buf) - return -1; - /* TODO: add ohter modes */ - - if (!decompressed_data) - return -1; + cfg.data_type = DATA_TYPE_IMAGETTE; + cfg.model_buf = model_of_data; + cfg.icu_new_model_buf = up_model_buf; + cfg.input_buf = decompressed_data; - /* cfg.data_type = info->data_type_used; */ cfg.cmp_mode = info->cmp_mode_used; cfg.model_value = info->model_value_used; cfg.round = info->round_used; @@ -2159,11 +2214,6 @@ int decompress_data(uint32_t *compressed_data, void *de_model_buf, cfg.samples = info->samples_used; cfg.icu_output_buf = compressed_data; cfg.buffer_length = cmp_bit_to_4byte(info->cmp_size); - cfg.input_buf = decompressed_data; - cfg.model_buf = de_model_buf; - size_decomp_data = decompressed_data_internal(&cfg); - if (size_decomp_data <= 0) - return -1; - else - return 0; + + return decompressed_data_internal(&cfg); } diff --git a/test/cmp_icu/test_decmp.c b/test/cmp_icu/test_decmp.c index 244bbe8..efcf3e1 100644 --- a/test/cmp_icu/test_decmp.c +++ b/test/cmp_icu/test_decmp.c @@ -110,7 +110,69 @@ void test_cmp_decmp_n_imagette_raw(void) } -/* +/** + * @test count_leading_ones + */ + +void test_count_leading_ones(void) +{ + unsigned int n_leading_bit; + uint32_t value; + + value = 0; + n_leading_bit = count_leading_ones(value); + TEST_ASSERT_EQUAL_INT(0, n_leading_bit); + + value = 0x7FFFFFFF; + n_leading_bit = count_leading_ones(value); + TEST_ASSERT_EQUAL_INT(0, n_leading_bit); + + value = 0x80000000; + n_leading_bit = count_leading_ones(value); + TEST_ASSERT_EQUAL_INT(1, n_leading_bit); + + value = 0xBFFFFFFF; + n_leading_bit = count_leading_ones(value); + TEST_ASSERT_EQUAL_INT(1, n_leading_bit); + + value = 0xFFFF0000; + n_leading_bit = count_leading_ones(value); + TEST_ASSERT_EQUAL_INT(16, n_leading_bit); + + value = 0xFFFF7FFF; + n_leading_bit = count_leading_ones(value); + TEST_ASSERT_EQUAL_INT(16, n_leading_bit); + + value = 0xFFFFFFFF; + n_leading_bit = count_leading_ones(value); + TEST_ASSERT_EQUAL_INT(32, n_leading_bit); +} + + +/** + * @test rice_decoder + */ + +void test_rice_decoder(void) +{ + int cw_len; + uint32_t code_word; + unsigned int m = ~0; /* we don't need this value */ + unsigned int log2_m; + unsigned int decoded_cw; + + /* test log_2 to big */ + code_word = 0xE0000000; + log2_m = 33; + cw_len = rice_decoder(code_word, m, log2_m, &decoded_cw); + TEST_ASSERT_EQUAL(0, cw_len); + log2_m = UINT_MAX; + cw_len = rice_decoder(code_word, m, log2_m, &decoded_cw); + TEST_ASSERT_EQUAL(0, cw_len); +} + + +/** * @test re_map_to_pos */ @@ -160,25 +222,6 @@ void test_re_map_to_pos(void) } -void test_rice_decoder(void) -{ - int cw_len; - uint32_t code_word; - unsigned int m = ~0;/* we don't need this value */ - unsigned int log2_m; - unsigned int decoded_cw; - - /* test log_2 to big */ - code_word = 0xE0000000; - log2_m = 33; - cw_len = rice_decoder(code_word, m, log2_m, &decoded_cw); - TEST_ASSERT_EQUAL(0, cw_len); - log2_m = UINT_MAX; - cw_len = rice_decoder(code_word, m, log2_m, &decoded_cw); - TEST_ASSERT_EQUAL(0, cw_len); -} - - void test_decode_normal(void) { uint32_t decoded_value = ~0; -- GitLab From d4eb52bb92ce2ed2150b74395b6ac4e0823cf52b Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Tue, 27 Sep 2022 16:49:17 +0200 Subject: [PATCH 41/46] add compression entity tests cases --- test/cmp_entity/meson.build | 12 ++++++++ test/cmp_entity/test_cmp_entity.c | 51 +++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 test/cmp_entity/meson.build create mode 100644 test/cmp_entity/test_cmp_entity.c diff --git a/test/cmp_entity/meson.build b/test/cmp_entity/meson.build new file mode 100644 index 0000000..7221912 --- /dev/null +++ b/test/cmp_entity/meson.build @@ -0,0 +1,12 @@ +test_case = files('test_cmp_entity.c') +test_runner = test_runner_generator.process(test_case) + +test_cmp_entity = executable('test_cmp_data_types', + test_case, test_runner, + include_directories : incdir, + link_with : cmp_lib, + dependencies : unity_dep, + build_by_default : false +) + +test('Compression Entity Unit Tests', test_cmp_entity) diff --git a/test/cmp_entity/test_cmp_entity.c b/test/cmp_entity/test_cmp_entity.c new file mode 100644 index 0000000..8493e32 --- /dev/null +++ b/test/cmp_entity/test_cmp_entity.c @@ -0,0 +1,51 @@ +#include <unity.h> + +#include <cmp_entity.h> +#include <cmp_io.h> + +void test_cmp_ent_get_data_buf(void) +{ + enum cmp_data_type data_type;/*TODO: implement: DATA_TYPE_F_CAM_OFFSET, DATA_TYPE_F_CAM_BACKGROUND */ + struct cmp_entity ent = {0}; + char *adr; + uint32_t s, hdr_size; + + for (data_type = DATA_TYPE_IMAGETTE; + data_type <=DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE; + data_type++) { + s = cmp_ent_create(&ent, data_type, 0, 0); + TEST_ASSERT_NOT_EQUAL_INT(0, s); + + adr = cmp_ent_get_data_buf(&ent); + TEST_ASSERT_NOT_NULL(adr); + + hdr_size = cmp_ent_cal_hdr_size(data_type, 0); + TEST_ASSERT_EQUAL_INT(hdr_size, adr-(char *)&ent); + } + + /* RAW mode test */ + for (data_type = DATA_TYPE_IMAGETTE; + data_type <=DATA_TYPE_F_CAM_IMAGETTE_ADAPTIVE; + data_type++) { + s = cmp_ent_create(&ent, data_type, 1, 0); + TEST_ASSERT_NOT_EQUAL_INT(0, s); + + adr = cmp_ent_get_data_buf(&ent); + TEST_ASSERT_NOT_NULL(adr); + + hdr_size = cmp_ent_cal_hdr_size(data_type, 1); + TEST_ASSERT_EQUAL_INT(hdr_size, adr-(char *)&ent); + } + + /* ent = NULL test */ + adr = cmp_ent_get_data_buf(NULL); + TEST_ASSERT_NULL(adr); + + /* compression data type not supported test */ + s = cmp_ent_set_data_type(&ent, DATA_TYPE_UNKNOWN, 0); + TEST_ASSERT_FALSE(s); + + adr = cmp_ent_get_data_buf(&ent); + TEST_ASSERT_NULL(adr); +} + -- GitLab From 8ba76f758c4252e3f517a4cd8745192e61a6f30a Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Thu, 29 Sep 2022 13:15:25 +0200 Subject: [PATCH 42/46] fix some issues with sparc and windows compiler --- lib/cmp_entity.c | 2 +- lib/cmp_io.c | 2 +- lib/decmp.c | 10 +++---- test/cmp_icu/test_cmp_decmp.c | 53 +++++++++++++++++++++++++++++++++-- test/meson.build | 2 +- 5 files changed, 59 insertions(+), 10 deletions(-) diff --git a/lib/cmp_entity.c b/lib/cmp_entity.c index b66216c..daa1e11 100644 --- a/lib/cmp_entity.c +++ b/lib/cmp_entity.c @@ -1692,7 +1692,7 @@ void *cmp_ent_get_data_buf(struct cmp_entity *ent) * compressed data is copied to (can be NULL) * @param data_buf_size size of the destination data buffer * - * @returns the size in bytes to store the compressed data; negative on error + * @returns the size in bytes to store the compressed data; -1 on error * * @note the destination and source buffer can overlap * @note converts the data to the correct endianness diff --git a/lib/cmp_io.c b/lib/cmp_io.c index d8772fd..0dfcc8d 100644 --- a/lib/cmp_io.c +++ b/lib/cmp_io.c @@ -1334,7 +1334,7 @@ ssize_t read_file8(const char *file_name, uint8_t *buf, uint32_t buf_size, int v fclose(fp); return 0; } - if (file_size < buf_size) { + if ((unsigned long)file_size < buf_size) { fprintf(stderr, "%s: %s: Error: The files do not contain enough data as requested.\n", PROGRAM_NAME, file_name); goto fail; } diff --git a/lib/decmp.c b/lib/decmp.c index 7bea1e1..3be77c9 100644 --- a/lib/decmp.c +++ b/lib/decmp.c @@ -40,7 +40,7 @@ extern struct cmp_max_used_bits max_used_bits; /* function pointer to a code word decoder function */ -typedef int (*decoder_ptr)(unsigned int, unsigned int, unsigned int, unsigned int *); +typedef int (*decoder_ptr)(uint32_t, unsigned int, unsigned int, uint32_t *); /* structure to hold a setup to encode a value */ struct decoder_setup { @@ -89,7 +89,7 @@ static unsigned int count_leading_ones(uint32_t value) */ static int rice_decoder(uint32_t code_word, unsigned int m, unsigned int log2_m, - unsigned int *decoded_cw) + uint32_t *decoded_cw) { unsigned int q; /* quotient code */ unsigned int ql; /* length of the quotient code */ @@ -138,8 +138,8 @@ static int rice_decoder(uint32_t code_word, unsigned int m, unsigned int log2_m, * 0 on failure */ -static int golomb_decoder(unsigned int code_word, unsigned int m, - unsigned int log2_m, unsigned int *decoded_cw) +static int golomb_decoder(uint32_t code_word, unsigned int m, + unsigned int log2_m, uint32_t *decoded_cw) { unsigned int q; /* quotient code */ unsigned int r1; /* remainder code group 1 */ @@ -219,7 +219,7 @@ static decoder_ptr select_decoder(unsigned int golomb_par) static int get_n_bits32(uint32_t *p_value, unsigned int n_bits, int bit_offset, uint32_t *bitstream_adr, unsigned int max_stream_len) { - const unsigned int *local_adr; + uint32_t *local_adr; unsigned int bitsLeft, bitsRight, localEndPos; unsigned int mask; int stream_len = (int)(n_bits + (unsigned int)bit_offset); /* overflow results in a negative return value */ diff --git a/test/cmp_icu/test_cmp_decmp.c b/test/cmp_icu/test_cmp_decmp.c index e961ffc..9eb587e 100644 --- a/test/cmp_icu/test_cmp_decmp.c +++ b/test/cmp_icu/test_cmp_decmp.c @@ -41,7 +41,7 @@ #define IMAX_BITS(m) ((m)/((m)%255+1) / 255%255*8 + 7-86/((m)%255+12)) #define RAND_MAX_WIDTH IMAX_BITS(RAND_MAX) -#define set_n_bits(n) (~(~0UL << (n))) +#define set_n_bits(n) (n!=32?~(~0UL << (n)):0xFFFFFFFF) /** @@ -588,7 +588,7 @@ void test_random_compression_decompression(void) void *data_to_compress2 = generate_random_test_data(samples, data_type); void *updated_model = calloc(1, cmp_cal_size_of_data(samples, data_type)); /* for (cmp_mode = CMP_MODE_RAW; cmp_mode <= CMP_MODE_STUFF; cmp_mode++) { */ - for (cmp_mode = CMP_MODE_RAW; cmp_mode < CMP_MODE_STUFF; cmp_mode++) { + for (cmp_mode = CMP_MODE_RAW; cmp_mode <= CMP_MODE_DIFF_MULTI; cmp_mode++) { /* printf("cmp_mode: %i\n", cmp_mode); */ cfg = cmp_cfg_icu_create(data_type, cmp_mode, model_value, CMP_LOSSLESS); @@ -612,3 +612,52 @@ void test_random_compression_decompression(void) free(updated_model); } } + +#define N_SAMPLES 5 + +void test_random_compression_decompression2(void) +{ + struct cmp_cfg cfg; + struct cmp_info info = {0}; + uint32_t cmp_buffer_size; + int s, i, cmp_size_bits; + void *compressed_data; + uint16_t *decompressed_data; + uint16_t data[N_SAMPLES] = {0, UINT16_MAX, INT16_MAX, 42, 23}; + + cfg = cmp_cfg_icu_create(DATA_TYPE_IMAGETTE, CMP_MODE_RAW, 8, CMP_LOSSLESS); + TEST_ASSERT_NOT_EQUAL_INT(cfg.data_type, DATA_TYPE_UNKNOWN); + + cmp_buffer_size = cmp_cfg_icu_buffers(&cfg, data, N_SAMPLES, NULL, NULL, + NULL, N_SAMPLES*CMP_BUFFER_FAKTOR); + compressed_data = malloc(cmp_buffer_size); + cmp_buffer_size = cmp_cfg_icu_buffers(&cfg, data, N_SAMPLES, NULL, NULL, + compressed_data, N_SAMPLES*CMP_BUFFER_FAKTOR); + TEST_ASSERT_EQUAL_INT(cmp_buffer_size, cmp_cal_size_of_data(CMP_BUFFER_FAKTOR*N_SAMPLES, DATA_TYPE_IMAGETTE)); + + cmp_size_bits = icu_compress_data(&cfg); + TEST_ASSERT(cmp_size_bits > 0); + info.cmp_size = cmp_size_bits; + info.cmp_mode_used = (uint8_t)cfg.cmp_mode; + info.model_value_used = (uint8_t)cfg.model_value; + info.round_used = (uint8_t)cfg.round; + info.spill_used = cfg.spill; + info.golomb_par_used = cfg.golomb_par; + info.samples_used = cfg.samples; + info.rdcu_new_model_adr_used = cfg.rdcu_new_model_adr; + info.rdcu_cmp_adr_used = cfg.rdcu_buffer_adr; + + s = decompress_rdcu_data(compressed_data, &info, NULL, NULL, NULL); + TEST_ASSERT(s > 0); + decompressed_data = malloc(s); + s = decompress_rdcu_data(compressed_data, &info, NULL, NULL, decompressed_data); + TEST_ASSERT(s > 0); + + for (i = 0; i < N_SAMPLES; i++) { + TEST_ASSERT_EQUAL_HEX16(data[i], decompressed_data[i]); + } + + + free(compressed_data); + free(decompressed_data); +} diff --git a/test/meson.build b/test/meson.build index 11ddac0..36fee6c 100644 --- a/test/meson.build +++ b/test/meson.build @@ -8,7 +8,7 @@ if checkpatch.found() '--ignore', 'SPDX_LICENSE_TAG,PREFER_DEFINED_ATTRIBUTE_MACRO,EMBEDDED_FILENAME,BLOCK_COMMENT_STYLE,EMBEDDED_FUNCTION_NAME', ] run_target('syntax-check', - command : [checkpatch, checkpatch_args, main, cmplib_sources]) + command : [checkpatch, checkpatch_args, main, cmplib_sources]) endif # add cppcheck inspector target -- GitLab From bba157525213e4f417defce60cc1c393ec1c053f Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Thu, 29 Sep 2022 15:39:27 +0200 Subject: [PATCH 43/46] generalize rand() function --- test/cmp_icu/test_decmp.c | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/test/cmp_icu/test_decmp.c b/test/cmp_icu/test_decmp.c index efcf3e1..6f2fe2d 100644 --- a/test/cmp_icu/test_decmp.c +++ b/test/cmp_icu/test_decmp.c @@ -10,6 +10,30 @@ #include "../../lib/decmp.c" /* .c file included to test static functions */ +#define IMAX_BITS(m) ((m)/((m)%255+1) / 255%255*8 + 7-86/((m)%255+12)) +#define RAND_MAX_WIDTH IMAX_BITS(RAND_MAX) + + +/** + * @brief generate a uint32_t random number + * + * @return a "random" uint32_t value + * @see https://stackoverflow.com/a/33021408 + */ + +uint32_t rand32(void) +{ + int i; + uint32_t r = 0; + + for (i = 0; i < 32; i += RAND_MAX_WIDTH) { + r <<= RAND_MAX_WIDTH; + r ^= (unsigned int) rand(); + } + return r; +} + + /** * returns the needed size of the compression entry header plus the max size of the * compressed data if ent == NULL if ent is set the size of the compression @@ -419,11 +443,12 @@ void test_cmp_decmp_s_fx_diff(void) int my_random(unsigned int min, unsigned int max) { - if (min > max) - TEST_ASSERT(0); - if (max-min > RAND_MAX) - TEST_ASSERT(0); - return min + rand() / (RAND_MAX / (max - min + 1) + 1); + TEST_ASSERT(min < max); + if (max-min < RAND_MAX) + return min + rand() / (RAND_MAX / (max - min + 1ULL) + 1); + else + return min + rand32() / (UINT32_MAX / (max - min + 1ULL) + 1); + } /* #include <cmp_io.h> */ -- GitLab From 23ced4e5b809621d37863fab963f7e787d4375cd Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Thu, 29 Sep 2022 16:02:23 +0200 Subject: [PATCH 44/46] set windows flags if system is cygwin --- meson.build | 3 ++- test/cmp_tool/meson.build | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index 6757167..9705be6 100644 --- a/meson.build +++ b/meson.build @@ -7,7 +7,8 @@ project('cmp_tool', 'c', add_project_arguments('-DDEBUGLEVEL=1', language : 'c') -if host_machine.system() == 'windows' and meson.get_compiler('c').get_id() == 'gcc' +if (host_machine.system() == 'windows' or host_machine.system() == 'cygwin') + and meson.get_compiler('c').get_id() == 'gcc' # by default, MinGW on win32 behaves as if it ignores __attribute__((packed)), # you need to add -mno-ms-bitfields to make it work as expected. # See: https://wintermade.it/blog/posts/__attribute__packed-on-windows-is-ignored-with-mingw.html diff --git a/test/cmp_tool/meson.build b/test/cmp_tool/meson.build index be04bb7..b53f4e0 100644 --- a/test/cmp_tool/meson.build +++ b/test/cmp_tool/meson.build @@ -8,5 +8,5 @@ if pytest.found() depends : cmp_tool_exe, workdir : meson.project_build_root()) else - message('Pytest framework not found! Skipping integration tests.') + message('Pytest framework not found! Skipping integration tests. Run pip install pytest.') endif -- GitLab From 638bf43d5a02b4827c15acb1ed7340bb59185d85 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Fri, 30 Sep 2022 18:01:34 +0200 Subject: [PATCH 45/46] add some documentation --- CHANGELOG.md | 16 ++++++++++++ INSTALL.md | 55 +++++++++++++++++++++++---------------- meson.build | 3 +-- test/cmp_tool/meson.build | 2 +- 4 files changed, 50 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18386d4..57a3c6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ All notable changes to this project will be documented in this file. ## [Unreleased] + +## [0.09] - 30-09-2022 +### Added +- decompression/compression for non-imagette data +- functions to create and configure a compression configuration +- add max_used_bits feature +- add max used bit version field to the compression entity +###Changed +- Change the build system form make to meson +- Change DEFAULT_CFG_MODEL and DEFAULT_CFG_DIFF to CMP_DIF_XXX constats +### Fixed +- now the adaptive compression size (ap1_cmp_size, ap2_cmp_size) is calculate when the --rdcu_par option is used + ## [0.08] - 19-01-2021 ### Added - Relax the requirements on the input format @@ -16,6 +29,9 @@ E.g. "# comment\n ABCD 1 2\n34B 12\n" are interpreted as {0xAB, 0xCD, ### Changed - update the header definition according to PLATO-UVIE-PL-UM-0001 Draft 6 - changed version_id from 16 to 32 bit in the generic header. Add spare bits to the adaptive imagette header and the non-imagette header, so that the compressed data start address is 4 byte-aligned. +### Fixed +- Fix a bug in the definition in imagette header +### Changed - Rename cmp_tool_lib.c to cmp_io.c ## [0.07] - 13-12-2021 diff --git a/INSTALL.md b/INSTALL.md index 3653d0a..7665bcb 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,15 +1,15 @@ -## Installation Instructions +# Installation Instructions +## Getting started -### Install git and python 3.6+ +### Install git and python 3.7+ If you're on Linux, you probably already have these. On macOS and Windows, you can use the -[official Python installer](https://www.python.org/downloads/). +[official Python installer](https://www.python.org/downloads). ### Install meson and ninja -Meson 0.56 or newer is required. - -You can get meson through the Python package manager or using: +Meson 0.56 or newer is required. +You can get meson through your package manager or using: ```pip3 install meson``` @@ -21,58 +21,67 @@ binary in your PATH. ### Get the Source Code -We use the version control system git to get a copy of the source code. +We use the version control system [git](https://git-scm.com/downloads) to get a copy of the source code. ``` git clone https://gitlab.phaidra.org/loidoltd15/cmp_tool.git cd cmp_tool ``` +## Build the cmp\_tool +### Build the cmp\_tool for Debugging -### Build the cmp_tool for Debugging - -You can build the cmp_tool running: +First, we create the `builddir` directory. Everything we build will be inside this directory. ``` -meson builddir +meson setup builddir ``` -This will automatically create the `builddir` directory and build **everything** **inside** it. +We change to the build directory and build the cmp_tool: ``` cd builddir -meson compile +meson compile cmp_tool ``` -Now you should find the cmp_tool executable in the folder. +Now you should find the cmp\_tool executable in the folder. ### Release Build -If you want to build an optimized release build run: +If you want to create an optimized release version, we can create a build directory for it: ``` -meson build_relase_dir --buildtype=release +meson setup build_relase_dir --buildtype=release cd build_relase_dir -meson compile +meson compile cmp_tool ``` You find the build executable in the `build_relase_dir` directory -### Cross-compiling to native Windows +### Build for Windows -To build the cmp_tool you can use the [Mingw-w64](https://www.mingw-w64.org). -Unfortunately, the cmp_tool does not support the Microsoft MSVC compiler. But with the Mingw-w64 GCC compiler, we can compile the cmp_tool for Windows. For this, you need the [Mingw-w64 tool chain](https://www.mingw-w64.org/downloads/). This also works on Linux and macOS. To compile for Windows, do this: +Unfortunately, the cmp\_tool does not support the Microsoft MSVC compiler. To build the cmp\_tool for Windows you can use the Mingw-w64 GCC compiler. +For this, you need the [Mingw-w64 toolchain](https://www.mingw-w64.org/downloads/). To compile on Windows, do this in the Cygwin64 Terminal: ``` -meson setup buiddir_win --cross-file=mingw-w64-64.txt +meson setup buiddir_win --native-file=mingw-w64-64.txt cd buiddir_win meson compile ``` +### Cross-compile for Windows +Cross-compile for Windows is also possible with the [Mingw-w64 toolchain](https://www.mingw-w64.org/downloads/). To cross-compile for Windows use the following commands: + +``` +meson setup buiddir_cross_win --cross-file=mingw-w64-64.txt +cd buiddir_cross_win +meson compile +``` + ## Tests ### External dependencies -To run the unit tests you need the [c unit testing framework](https://sourceforge.net/projects/cunit/). -To run the integration tests you need the [pytest](https://docs.pytest.org/en/7.0.x/index.html) framework. The easiest way to install pytest is with `pip3`: +To run the unit tests you need the [ruby interpreter](https://www.ruby-lang.org/en/documentation/installation/). +To run the cmp\_tool interface test you need the [pytest](https://docs.pytest.org/en/7.0.x/index.html) framework. The easiest way to install pytest is with `pip3`: ``` pip3 install pytest diff --git a/meson.build b/meson.build index 9705be6..53603ee 100644 --- a/meson.build +++ b/meson.build @@ -7,8 +7,7 @@ project('cmp_tool', 'c', add_project_arguments('-DDEBUGLEVEL=1', language : 'c') -if (host_machine.system() == 'windows' or host_machine.system() == 'cygwin') - and meson.get_compiler('c').get_id() == 'gcc' +if (host_machine.system() == 'windows' or host_machine.system() == 'cygwin') and meson.get_compiler('c').get_id() == 'gcc' # by default, MinGW on win32 behaves as if it ignores __attribute__((packed)), # you need to add -mno-ms-bitfields to make it work as expected. # See: https://wintermade.it/blog/posts/__attribute__packed-on-windows-is-ignored-with-mingw.html diff --git a/test/cmp_tool/meson.build b/test/cmp_tool/meson.build index b53f4e0..065cc45 100644 --- a/test/cmp_tool/meson.build +++ b/test/cmp_tool/meson.build @@ -2,7 +2,7 @@ int_test_file = files('cmp_tool_integration_test.py') pytest = find_program('pytest', required : false) if pytest.found() - test('Integration Test', + test('cmp_tool Interface Test', pytest, args : ['--color=yes', '-vvv', int_test_file], depends : cmp_tool_exe, -- GitLab From 4839a0411dc6c3a68c929c30cb74abacaf71bff4 Mon Sep 17 00:00:00 2001 From: Dominik Loidolt <dominik.loidolt@univie.ac.at> Date: Fri, 30 Sep 2022 18:35:04 +0200 Subject: [PATCH 46/46] Data Compression User Manual --- ..._0r6.pdf => PLATO-UVIE-PL-UM-0001_1r0.pdf} | Bin 746694 -> 795667 bytes 1 file changed, 0 insertions(+), 0 deletions(-) rename doc/{PLATO-UVIE-PL-UM-0001_0r6.pdf => PLATO-UVIE-PL-UM-0001_1r0.pdf} (71%) diff --git a/doc/PLATO-UVIE-PL-UM-0001_0r6.pdf b/doc/PLATO-UVIE-PL-UM-0001_1r0.pdf similarity index 71% rename from doc/PLATO-UVIE-PL-UM-0001_0r6.pdf rename to doc/PLATO-UVIE-PL-UM-0001_1r0.pdf index 581a47061c76f787b75e04ccf88a5064f370704e..bc2bb546c36edbf8fcfa043a9735e321cf0597e4 100644 GIT binary patch delta 234219 zcmX?hQg`wRgN7}PcmCCz7~9!#6_*sHCgyTg+}avuyZ*Kn&)whJU)a}pP0_gNuwbD> zbz`3HCfhv+zpK6c(sup7{gfpdH@)UO;?os!p0<WxPVQ63v--!qi|3n5zIcD;cKwy= zh0ojV^mo@~&*j~6=8?_p_xj2IpYwlynPXSy_*i^516%RFnJ;p`b<cd3F|q#E=cn^0 z7dM=9)tDH!ai8A<+xm{9)^epLRZFj37Je1s^F{8paQNKfY;9+)ic9^TVq10Br+V#? zsj#Z`{8gv<f8PFC=b!WW^_)Luz5BtQH@)8<tm$8Itv%p;M`e11#^!yW6eku`y!{<+ z>G5Y%?%d61IZD>O^K^LV#6RWCH8!0IJL^kRKQ+ePnQC}xa;DJ2_9e-iHn{AceRx9P z?|bj%0=(HKOrB-L7}&B>a*B5g-@ALS-@6=bGdp$UeP`su+>?vX-CY&=&ZsXa|C!vL zS6ZLA*VXz37~E;Q&(xJ+e6;LyL8KpZpiGPmXZ5=YeT9c&WS<mU`rKz1fBi&k?;HJ2 zw!HgN^$bbg5>q4puzsDJm}IYZdy4G|&4a%zXC+LZ^L2^3Pt_7X50}&p47RtE9)5nb zNMdos<Hzy`We)LuPW3jv8T!GCBi*-@sbjaz#1%|m!+aOJ|M{F_ta`ut!}65Jliq)H zemF%)_v75W5>q*i&X4Ce%;!$%HMsNoY<Bpf?Ym35yu2>dTcilDbLXpYvXgr9aLJ9@ z-d_x2Vl{JSXRiBfBd(jEqJHz{;n)}Vp0Z{-U4D_B?Aye-aiP$oV*b1rMmi<!zUNbK zyC&DKow8zu%f$|@nW4O^mgK*5Hqmv@a!E5;w2E=D<-(1--);G+X3n=eHOQYOBIncR zY5KB~zmyj44l8fjcD-IF!p(op>xEX9#=2(9cDxjLP?mi~H&mx@y}8BK^?S{yw|iW^ z9rEW^^Ug;rEZrH`GnZIDwB08(?cmQfUU!$AO5vTbl}(2|+yB|V4!xqx{8DDIa!b{c z)9br@w)a;m@S7^TZShsOt^3JD<Z{`UFcu!^<+)2_Hg1?}#dhg2n}Z@ly}72%trKbt zd-WFl+%0h+cH39&2a>Bcx>RXY?1);i^Ye-oD?1!_hn!Ni*?;!-nmWyH;aQE=pRyK& zwy&9668P86)#|Qz!ug<bi>MaMm11$*F1(%|w%^ZV*5!hKjHbt3OyZA3379oS_xTjb zp82}2<?pL^MW2$wH`lI_{pgZc^IFa6obNgpGpR|lRh~Aw88nngJuN={A?j9U?2gbg zrW23P5;;8M%62to{?-#~Hayz6&fe<h`c<2!G?qWD%-&!7gO$-`%hu@&nV3Wv&89yT zVp6R)FttR^&u?dY_un=XXn6lu^cQp5&lwMyIchX+vAxiJd#_7w&W+jL%}RT}yf6PD z;u*Q^Nyg;6y%Qz0PYQYR?ysCAXUCuP;EO+x+ogKDnjbFd`7b{k*4~@;`8+c-pIbZo zrG*7&iVWW@N#fg5|Lb_Xy{AOozx0ZF^FLo4(k(2WZMkyz@MDEFDGg-?Jfg7{|9-X= zt6EHV|8-*T#eXv2Fa6kGvtPK2??<PfNiEOak_G*K*AF#U3tYARwB>%HmH47xC;rCs z@8`8{<Y{cv)+xTURM6vz%oDj60*@d1aou0lviHG1EmMvwN<rQ$9+@t<C-8`!@8!JX zf0oo=s<!X>u#}Jgsaj^kKV8RPw_i%n?dwl$IHai-Bd~gsS^s;3GSPR}cbYo-e>(o7 z>(7hzr;4VYTO&TdX8t@S$;S)tES{9`Ctmbdn2Pkuwz)!Qq5{*>^$q*4o7Jwq6IM|w zeCx;4gNF}_I3B%l>*4q9Z-4HWUmn0vnqw-~mUP@{dSiX78+X&GNSS}q2aV5YSk_1} z=yaZ&s$I`tFVLiXn%nIuo9Ei7$4skNGJ8Lg2yEPYF8@tB|KXMMA62|kUn&22(Ymt= z7R4E}*R@XPnWZE6=n|ug=*CvZsW*Jqu<=wl@On00itW#AX$)D@$?JbfcvF**LA>Hp zp3UnYwOedkcvOS6URYYGb)#eXqrFz<0&I>)X8urpqVNBFiOE!^h7_k6of6k3g_*82 znS83!c)^U4O&n&Kf}9ZrxsR&ZPcXXm`b){>2+8T~bHBXugPXg<{ngh4vOXOWwAlV) z-|vfV?%~TXDR&<8VK9<@Wph*7S4?l~noZ}bT|Ye$Y@8tzddcm{OX+%_ws~J7UzqP* zv_5c08BZB=YjIzvlAh^B^CDxVANQZ=H1~B{e0kwwoj&>eAMIVgs`I}p+@CJ{O!M}O zy<OX0?79?{BD6vJXRP-8xXn^QZ+yE-#T+$FoR7?NG&PzrXXon0b3~+9@g_Te(mi-n zF4;v?!2g8!_czB2cIlb^_nV~jvi{xEcQfww#JiuI_D7Q|f9>m^&8Popib_rqnADZ? zB!=f&hxBya{k@FS))>ZcXD1c1mY?;$yL0#4-P8IGx7}Ho;2>FfTsCfFb^AO^R>R`6 zd=_`y)|EVeKKn&*e81hfnT`L>zWexPo5QvnGTXWfmM&xtykw~%_%7qhnT#uEI;Nft zsc$_qY4)5YRYtkKYHCJ?lU6>T^&#Pw=8SKXxcT{i6>OD?@DY6~krB3X!};^#sk@h~ zU(zM~@kG#`UyB%GtPZP8+rhO=a((ZoQ(Jptwl#@N`>{sJa{lerNuh?F^A4=M@}+d; zS&K!_{=U1lFX}y~NV^5&(!=k=(wCf^@gv8JbHSNQKJ^A`3Z^XBud`=f_l>=m*t!F* z7r&Zg`Lylk{fBWoLtcH{+yAd;_m70j+_e=ocg*-N9z9(8QQME{(kAf>+z%=&Bk$hH z_nkIrig}mirYmA8b60V2yk6w2z!bF8bSvl1zeV4TL{ylH(>}Sazq4fRffaLKtTkkE z|2co&VGg$*HG>w{IrTX<8gkQa&X&7$sZ)CT48D^mZ#`XC$fF@;UGl1Z0?$NE#>$8v z7q{P7<R-TMwAj|mI?hg8WL;+MDlF!Cc;bTLTD1>aoK`(6|3{oWA~V(Z!NlN-2SG7c z4-~)o?f0SMQdnr|+H*-a7s}sI+R-wf@%G6VGdFH+TAa4b{E)`+H>a4ECe+_3HVM;t z-aT3C+?mC9xNo*y@fYjjPL{B^%>P@itKL^8I?2o|+oA8EU(DiJ$KDl3sOdiwU-PJS z!@4!$>eDJ)E$vFDeNEk1RrnxrV}{M%3qO^Vm;W&nzv}r$dfL)y!dK3HyvD$Jd`Yuq z)0NVunw9RM+q5cHCC^)^CUbtb{5c<+`ZJ~*44lII8Vmpau4!jIAsVwac)w}HmEMVY zd~22lR`1?cb8xrYmUR(xcb>2*nUr(xijgkUg$T9Q*Y|%PRo=$5yzG#zUzPJ54Oa20 zYks|qd+rxW=y=%nKb~f=>y`PllQDi<KTmCuI-FYb@an$vp$aywrzbgF3cuL>=%&!N zra5KxdE3>FbF`g1H*xXx@P%G$l&`V>iLK-k|9(9}JtA|i*WU%A$5yYBFZsO3)b*O7 z*Z=1lM)%tm#jMrPb}`Ej%e-?>ZBFay<Ez&59x{6{iIaP8#rmS1O<iU8%$70jRNHG* z9CG8Lc4@U6%ZVy)?PaO`Gb2AHnwZYpduGSsC2yAPHb|}Cw53-o*G;S=|Jm)CoRiL( zvxrM+*_D+Z&U|lu{aU_h8sDa<^QEDgovX?d&Clv5ZxZnhC}MjQXY!M6+aA%{3sIY6 z1^K5YmIWPNT~<5y{)5sF=1Z<F`M6y?^L~CDkHqFjg<AzLUAnrsZd>*@Kf}l051TKl z44nCiYsajY_dC`;608@Pxn;5q>oU=uTXr)4jQGNDydrIyz=vBxPMq3rcZU5=ZJDY) z|84c%a64n~w<Y<!lP4~jmdCejqeb|dvx+;f8hm{z?l{#`^Jdo8gB!M6K8))7T5#-{ zPTcuJOO<!`o}VMUeNq*xWuf`k=;o&ui*MxetyyIe?!Lmva^)XsG2@$|yXvnki#@iY zWoGK+e_b1G7J9`jpA;1MFh&3Q*{loN23hJ+dAsZ5iiPuD8Lawdcz=)M&(@><v$kCQ zAFzGuiSm61E<D^QSaY*2)01=2oRE})leg4ee;D89ylgK1BKXy{`%*`@CA%)WJ0)zl zZT-3X?BA#VT2;?1vwn?1-qX-<iS24%^)#gAuUCKmBjvxW?2eIwUs+TnS8#mNtxq|n z+o!#l^J-iD{l6dVQ#9OcCtqX{p8kQ0Nxj~}%nWyn!SfuCe8WQLi)Ie_ncu^{JV+}q z&J0*G{oh|c`9&_L_)lu@c1%)fQ;l47;-R^J(eCqeU#a{Lo_qCAe3`u6$D7vwYR=3r zfBL6bJz$aP$Htl!dkTU|t$7bE=Jc=s|5?5FZhw7w{rlbW^M3SL>F>F?th(y$l*>#f zOTz>{v_8N8J*a6?ko{-(@7G$2Uv1^Rzv;f_1LmKH)Z6T*<y(C?{v`Y3LAk3><o;fI zuQ6ZS^yiCz{mbKb%~f(daW~Yj;%za%iD&07ce^DeJ1%Q&S+z0TDCQ~K#AubgPK8+K zI_bn;4;1fzwzA=@U&ep8!+zF}3l~(%4SIx1cg(Ff{Q1MOETB^0l>5e}GUxw%k5@nX zo0j4Gaq=fq-axzd)w^Pz8jBxU{{M{s#-5h}?;3SxeBk|f_rRu$7v4H>J`b5WH}3L< z`0%~b?b{w*$jy1Yd^tlqLz~xx;=AkLzxrHncW$9W-A3aO2`~MyWhFZGW>Eqx?^paT zXF1Qsz3cZCMJutn`r-dx)g`mIPZR&@<@xaB&kMQ+PR!=%5|Jz0;}XRzx*a`}@3WnB z+4jdp@UwT_gsRQ+KTbZcwC2L^KS@m=CWS?PcFcUq@~Pu>AZx^qsWmP4&abQ8>@Kd_ zb*582%d+#;RlB`%PK#NT%=2b`OR6_?=lE<ct+Uin%HM~9?MBm~WJ9i7s;4CcYvc-D zHOg6*eOLdmONZmB$BW2#d560)duH)!oXgX#*f#5IPMoMTPZ`(F!dK;T%+j~SH;d*d z6m37b^l-`5)YGCe!VaG{N-a*lleu#9-IC(nFa2hpO;*~t*2AJMqnh_(<l16?cbUfe zODa7FmAX4W$$n({b9uF=-Q+V@lzFDrbTCX{V`X_$+UdCIVtd8Hid!Eh%ST=O>hhyd zsbEgWqxQaYM^+i6uT*7t*&4R?z`h9Y4KbIcm=v;(%+9TO$nEIyK6>AR53Dy09t#); znSE(gDxWp+=bG~w+6VJfM7>*#Y8QJ8o)YK@yI6nn=>cVya=zPvc6Sa(v1EHFv@M$5 zDk&3KkY;9FXwYrkbhEMPj<nMQRe`?mM-0q2bXc0}l&61A7n|Urv1cytH*NJo)<je0 zMuXjoiPwrAJ@*r2OIE3_UL1NRIXjH|`nL8Te3N@~4y@brza}BbIDM(Z>RS)%UC*q2 zuv$&w!&9^Rl11|@c$=r`&#}0e921*=@Qb3!-t!GN_Jz1GB^-1)s^b0K;P^AHxU01l ztfFeCw?6uFs3P`YjIV3Lx%~9Y5kKZM-jpmZUSC;!>Qc)0tovq*&RYc4E>$*-x)ETo z=J@|mt4mK;m>T5tN_i|?Yu75Z#w|BN<N1>G)!mZHl9RY)X4muTua|$q-Ol=_p{w;) zTEP7huMJI`XSE!-ndp4(0++V`ER#7cnak${vK*hYK29Zl&Z)j$=eUULJN8S*g=Mfm zdD;0|c&f^KzVBQMUTGfB<+|Y<vy7Lsbroxu-jURW^JLZ3&b0Y$77x4dPK-lyee3rN z-~I<~+acOws<tE7FuM9d{gr8_xE=?beRd!t{dnt{xr<^-d85v!9p6&=HsElW`MN~& zxksxkzeL%#6rBBWQYkz7$UL2Z$>&**O<&`s@2da$>)R!jfl3qFy-&Yc@<<`5ZRbks zGOm^Fm+pEBO8b4dk@QDe<BHxJ`Akh2@4E;8PJXlbU+T69<;z@YoZ%ancN^x_@0|IH zVZobE3g$}P|9mzy-BjQSQ~I9yJXrr>NkLJlL7aNN`?NZ~qtS7E?mSBltef_SHzbQS z$&@8x{iVHEuQ5v|)@mBx+*DL~C*`(DVphEpx4-u^!#N&jcpF)sE_oi1w<$CH_ymim z2QI5dOuPL*jUhtcEOu?B*v|z^c+1lMpRVU)n!I_BbKHp|yjiK9br(*Yf4kgza?%s~ z*oRTO4w)>O{8d2y<rAaSH9HTio^IB!rR=5qC~y6hUD~Hkbk6eM+E(cMCsiu3(QDS^ zrd0{Q6ea&1JhDw{wz%{3*O8aa*9gDN{@UZN^u*$Uay9EDOMS^x?T^wz=0EbEu|l$s z^Tno(*XkX`q#W<e-WA?o@rwJ(;aSpmmaPh2z@mL2*!!tOS?k=z8|9iS`PZIH`xUyp zi)+@cYs;VPy4z-O_l4BQrI~k(?*A~_zx~!uv;3Evj2*PMY<oLl|I95(tOcgRTYMMT zUlBCa*>;#`$IVkGat}uD)7|#!z4F&PP3yZF)_&X+bzq0$>ACfHd^hZE)oktF#ovGE z=>N0Py)3!WI~<-{YJS!&`0;HA-^(j*ffKG8ow!t^@n*9_{%t$^ggxp#rg|l%osU<1 zRX*v+cV^G)n){9_9=)FBJ{$RRe}=_3>+e<6)s(EZo#1|KU&q?CZ<({to@mwWJ=C*% z%bl6MZ0enoSq}AS%G(d5r50{jz<KNX`iV~tZ@W8f*DUT^+x%Qu=KSRg)i^75Mp5T~ z@nr7PMc-l?gQo3Y_c>WsOU!A$|EB6a3qtsnx-#y{yUU69CZAoR=zn=l!@0|~*Y(Ur zgG}fAI6lw4OnS-t@Av<*|69ZS8C2aenS%T1YW2nj$QAD0sFUkrcJkhRuKkXG%A&?A z3?f+<J(3i5`|aW0RPpZlE8iZs>dE){O#>Fa3z!_A&$K)4?rI^`Nmg@IbocA0UHo!h z=isIKy?;LS-m|;-{Q9?_SMJxxi)3)@6WHQDIpj*3W#n?9A3lGd@2}mw;m_0UPoF=n zUw-~en|IL3=>0YO^wgqQC8jU95P10a{&P{*i?`M;@88QZ|Nh4$=YQ9xPcHnjVE?7= z<^Bu&^1pmsyH+6XK!@G8*H2sC-j(>cae96Izu!ehD+D@Xg+n*3EKCT@-XOlfeNDf5 z_NNtMdT!6dqy#ctmQ1{G)oj5{fmiJJqULJ{Th*Ve&zT}$tN2K<^PrCEjXQJeKED0A z*C1n3u$t&rE9sg0w;P7Od|zqeJmpuE>y`IAuYL=w>W-}a=Dk=+_Hp$4$Fq_?ZPzZg zzSLWK`lO`j;+Lz7H=g)&EHm%P#;z9&dp=%2FDY|MaD~XR9e*BQ{`2qebzO}HwLUrP zwpT}vl^;8`K(<M*-Zkc*b%S_FLD|o33@$;juXO)M{__-&iKuH>y(L1*v@K-AEwv60 zS?h>Q#fLFh13n1ddL=F*kZ@jzQ**U_VD?3Y^-5tGFW9>8bY4qU&3LG;<F>k`@X>LL zN#~cG3rIYcUm;l|G53o@rA4_|$GU`b48B4cx7f_9HF?r6h*W!=sn`AMSy~e-+1Hq$ zrBQf;)nTc|oEc`99%u!pOK>pyhef9<Pw4QB+>#U!6vCc(LMT+x<LJ5vbLVADV*AUJ zX~rdd%lNSE|9@d6Nw;oni<0v{x9PyyOx^Dne1qa`U8c=jQoJ{L-qKZ%Ce6D3V%@Se zQJDu{&23}aq<ZAI=z~7d58qbRGoAW0TkDzLX{TL06VF7)zF^aP`K`<Ql%3@pPbs<S zB5g||r35DJn!4oa?_|}t)1r5kh0ln6ZSelroVnFyGj1e#l%9X4Q`dS@P~^;ve{p-x zbj`GL+7ZH1l{V3Cou;n-aeey^b(yGhQ7#Kpe=u)-^C$9$d)|w(r_%j;bK29jqBQGu z&9=-@5R<<a)qY$}>bB0;T(c92I}TK77+YuP{9YEmX!o@Ab4FcDs+K0q^v*GEHfVo2 zarT}Z;ks>{^@soNe5EU2P#=)o#j^Z@gyw>_e~E7@S?6-@N#OakeVUc1?Q^+_s+IYc zo$HK`P7J@gu*Q4ADXu++jQouYD<b7qK6PzfS)Y@|_eQKZ@A})kY55&>#xJvbE<LU^ zbv~<jo4<3fU3KrSd1o{nEZ+Za`u%F1@r?^XTh=W8ks_t`NMB+x)6x`m?x@NXhFLcZ zSaw^UvT1Qmjg8`8c)z|u+g-J}vpwb^a}49ECubPL)x9gzm+bj0!0Nx|#NLVzyX11Z zP9=uvHhrp>isXMY`*5x8mnk>hTVKeP8zl0vUpjb0mi@6@v4L{#gNVo)%UxT`*Xz!? zS|@o>!1ZgPM@DYx6TKqtoeAmHU7LarO)d0Z+3CZ)aD9C#&z-d@AxTwyahEoiTwr>h z#i}|-Hu<E`rvLA6GVb?%bmhJ>`|63wjF)=@-&M%2-u?MVTm2m?`QOpn59($oO0M@d zKlr(>Bqe{R77x?!h_Zh&)^2ZaK703+yYxtMW~GGNjSelR)oagBoxcCs%J0kXiN1As z{Z8lf=S9hP@6Qm|Ue$K?()AMmC-eDPmIW?d!xUT^bl39cv=xVA?v~{E^sV>X)cx+& z$8EQnAFNWZoo#&c!nVvRz50N|;`7+Id3MdRj?aI#*?gP3ky^9$`Z-etKR&KJe$=L3 z(ADPE=P=(JcTUUbM};Y!-PXn?b=2qInVdg2?^dn8Cf)Y2Ei?CND#z>_vadwftZVno zJz1TcF4zA#hUWm^<?mk0^PZlKeg5b~b&O#*<N3qo*SEXpCOkUcIcufvO}z&B`f{e8 zq~;Cz$zR*|#C~ErJ~iS;V#3*)x2Y5CBZ?n?Tx$9@@4#D~OySQHRj=lsX^+=UmtLYN za(ds4*@wKQ-#I^T&DN$9d&`!~J?hUa|GVYE!qlAC7t-GF$*(o#F)nwPK49Bp|8(c} z!zcbOS-8yN8;}2!lT$uV*%!3PXI;;C){Sqz)b}z7$=%$T^zg8X<C@1m=NavIw(;-l zmunnX2QAnlw<g@G&}i4)k9QTnevvsmt0eV-YO;Q2)YM~MnX__UR~cM$P@VL!e3|$$ z4dv7+e7pDgCR)$?opt)chI_dcIi;t<Z{F}<se7{9JEoD@ZRU&eMLTY+{qZ36_<{q0 z4C>#ew9ON{Umsu6f2wthQcU_v)AfR(QjUju_nz3ezu@iHyQ>OjuPK||XuE03vz2QT zC(iEMldXJN>h|j1Be74;D`q~9ypg#rSy9}5Ug_~)JdyIcE$mMH1?F<wEFRjNxjI?! zv#Pw;&8lM;zGk}oV|RCc;>B~WKdJirk&aby%+oB?+c{MZZ9l47zpBHqZ&_8^m&Gc2 z^9`I98OcuYsNxR%A@U=^>*1tw<xlq~ESPbA)ywK<cGq7O*|eY1(VbQMe^<;=$?M<F zbfobdOHWF=)?IV+Tdr{7qGg}$I-cy=zURo!ixYge_NNPL1wC85CpgUSqt2Qc=a(Oj zQ4>!HpE5;ho!GWz^SeY(ODz0VFO>c{pK)5|o6rSaYgY(~Wvq8!FZlPtnYZsINWV=_ z__=4l@fG`7YJcA!nCzi+faUzPFAG&x=-imO#H^ObbED7|PfMqU4YPY@`)&N{!6@>K z*XfE$(S3>2x9x6bN<4VY@J-@V@})$U?doq|%t<dz`oBcA_E7ilxYr9y?#I1}SmS-D ze%HdkQ|HTEdCfehI3(OJY1yvUcWx{X&raE=dQT>EpDN?pHBE)li|(&#NJ?31q?2_x zN+Ergp_g66iiZ;|CyPs4)G?MDd^+Z6IdxAWL$YCu?}@879_dU@P}j70ysTew<%JT* zpD$QUj9oiE+L`e7_B<1q^^VQt_Y$Es+tp+*%t@=aZCciBx$)uSvsbsdzB`$@@lNi< zt1Eq0@7TZp(`D`(wO!W#dCCuKImkyH&HMObO~lULtxICFH<n(nNi}@<sLJitk+_Q? zX*v0Co*aCn`}<|bOrI==wBr&TD#uRx7hgWDvOUOSxsgqCAY)+4gm)IrEHiCS8OK(N z)y;TZpCi^ep|!p@q$cW`(E?dtYpv==l>pB&rF|kZKW)D^fpg8)^Xe0Srit=DcZiJY zO5g0EqCe;6?)h#_&IhaWD|>(XyeqzVgRRD_U*%18^7S{*T`v9IuQo|+?uGjNnP2y_ zW-KeP^K2-(%s!b#{<_M}m&qQho<51rWovzz()Hx<+Jw0|ZIWAA>($>~pBnM@-I9r- zpK33i<jOmH>!GE;w%*aUt4kxwQrVwdt&fkItMOcSQrU$E%R)Y}=e@i3x1q3lyIVlJ z=qvW^<@^cvO6?TuU(~SswRL4M3(twVe0)O4p2o$e6uekw)l9!~*L{KO(q{XX1-n@f z+O2kY`ZklD<#}a(g23sW*^$9%8r$badp|9`ylXG-T{eq-ymO_lY>0`g<;a>TEB@Mb z!*acj<5eyFM}K}UeB}76{ISrs*?NiYm7La}IiJ6LlQf58$JyMIKX+Z@ee?aO*fRz3 zYW5Q@OjTt|*Jjox@2dO%XZe3d@j1%Rz)emw1JJaPO1+_xsi7UxJnzv6?|fx*o_*iL zU+_nWx(4zem;oN-x&18c%Y$nl<D^7(PyhSdJE;HSjdLnD*dkPf`!hZ&zWnW>^=vZZ z;~(rkpZ>f)V}IVJ>cR8mYPn-SCT~30DiXSIuhYhxjg}Q#R9!Cq`*=NF{>=IRGx?t_ zcdwV5sJYEW{_NwU4&TfI%uWhzdhqA@MN5|Fe`d<>U!sv5RWr$dy9~q6qqZLvK25H; z)H(k~%BmeKGoJle{?s9t$H2Dk=j)H=#SEK7EMiV<`*BI>!;_c~-Vef8t!L*?y4ub< z$Kz%~Lidqr9IJIUm~(yQTyFOKlBuDdP+Og3=*rno80u4}9z0$0Pi@=x&1@X3x*~VO zzU(|+9u@oY&yBT=*}J@B7g={-;n(cnlr#VT?#z#Jmov{C+-CTx^ObaEzRfJ=rn8dA zUh2%N+Ee-1x-5FvmnCL(l_CWZDFKnEZvOeVLAL&11$WnhzTzc~3tve*;_&I^<48K0 zvTw}+Z?lEw=ha%y)JKQ6J&FI!?))LlFm1MzQM-ihvm;%x4n4j)vFC;CCqBv7auhF8 z{iPEY8JYC%nnA?;ZpKK?_1{j$ER!gCda_}2YlV=$?PgVh-2A<tFBew7(w-moW&-mP z%}<IymtAnRPLlXmqbU7nb~1Z-4oki0o(<sv%cm^pvO31Y@l14~-^=>Osp37eieyAi zz74GqoKaPfQxPnC|K=eVo-^t(qRBgw_X-(o4OkcXYu3rxcaI0HQJ+&*?_sukKcmz~ zw=SlqH$<3zDpvZpPC7ib$zxx4)1=OgPl9IN5S`p=xc_Mbr>|y#Q<{#N+3CyDGXiFA zn8wIqy+uMVe9@cVG934;=DrHtRlmwz?l6b<sb;lbYPq2-&$WV<UtT1+f6Y<ngcS!) z`|_3gEcwNis*|tVH_2dv?$46-26|6IHXf=Kar0ci{N~gU-l+z*0i`!q7ERdl;MF;6 zy}73LSI$mi4Orh|;^nNbmsQm6{rE&!*;)6pZs)KMn&&NYCVdYGn8OsV=wqQ6ekrNm zYrT@Vj5OZ|rIPPPzh0HNeV%u{@$TB_#s<0iYmcobIP<1iw#>U-(|phK;f>4dmpPo> zyOv*S*TO@=`*R|{T)MBmqyI^iq3cxZ|ML4vwGFR`-uY4*_=qQXatZ5|bFb#!<22tJ z{P@@vdtce;`v#HKI~}e2>^|Gwx*j}_bHlt(x>fbA&6DOoyzTmaagd^aLh_tw#fu)C zamQ^orMkWLp3z@DLq5*j<VNxCrn<+|4>B>|%gjB$^gF|ZKWggI@1CYxeA~8dtL)Za ztGB*%Gbrb~8X=XiVtR9)&+_SQ#&cIaN&IvA^~$Xe*O^{1QEQzV8_eypmw8oV-1-H} z-<t=$)ic~OVQ&5FhnmvX%IuG)1-ug#hzN{-dD)ZI<g)vp*JW*D?90Vo-EO;neKGI! zgnn;kTj{E`FLoa02|HPMIlT1Z?-e1hRCeAx;G_1AU-o`}oN92&>Bm7;-pjVQFMC+} z==#3fQ+=ZMTfgOZvRbDh@Z>~JW~0C+GoGz+33A2xp5gl^Oj^QOUSAVEd&v#;FWj37 zmPEeq+%Z)<cM*#v*Ye8+ow}DVd*w>G#Y~i}J+l4wg(zd+_iuN-^ZUN?(<fV*XkE** zTc6}!eIN6GvEuJ#=Jxx4Jl-boYp1}WMXT>zFxZmyO*X@#z}&F-*j-t_k4we3O<6T5 zKP2LRbf~@kpAVu<kH3H7O3h0to@~a+$7nSDBNvluy^*1@IdW0BHtO`cJ39pLKG*)j ze0IXu2h0XrI3`J)$P;?sq+?b5e(qU`gDI2m`+r=zOylM<w-((yd0RbX#r#G6MR)$$ zHpBI_{bq{^|EqrfTwU{9=eqvWUwdl*=LRl0dGpGG8WYFL+-d9!xKff|+yDEjec{vd z|2p;dy7j-5?pJVXMQk`dz5i%Mz>&JbuE4xKe?L#Yma(PHd}l(bS>1Edm_Mtpw>$n^ zaQ|ud)YB)O{deZ<oG!I4Wmfuw+S-Djc|OGlf34TAkCtl_oqWJFj+KLzal<th?UU~3 z<mD5R);p}e_;Fo{$0cQN|DK<6CvVUA<a++o8TZ`Ivie?wOZ{f1t75qhSa>!4*zqUn z^z-d37g$2nR5mO9=z9ONDzxg}wR=5VuE<w*{L;PG`gUE-=ZiHT-e2;{IdlH4q)uCB zz2RQzqh<}KnR`Fo%h|k{DSS`e_q_1mUe4t!`}fy$J_*oR67=Tpr|;)~zyJUDsR!32 z@#@pk?Vq}P?=~#;t2gIL`mpEEdsmy~GKW5uDt=1Zwc+m1(x2@CAC5kKzgWtJ_fyz= zr$<GM4<hC61f*Y%H+0RgUb>)V+Hbp~iaPaGf|?iAH*MTt?6>FPgpQcHsp@Z|)gm6L zZI|)Ym2ZoZ61r|J@IGN*Tui+DM6WkR_8Ub%M>%>vwn)&ua?&_5Q=6sU^G}A{*&Vwh z=bBiug(Nt?eIl_ulw-^6xw=w4lefN%jCl6o!e*BBPefewS5NJjkR)Nh-}KuTw(Sxx zdutytY$($E6mgn;FRP}8;QLC^->cJKPhl&6Q|T+<yDw>u$7G-1D=K;l(@Zr_Y<>K7 z_1W#)7JQ6bm!3FleWLOw<;$;?nIF_&H8W{Hkx{f%TuWqEg5AT0)Npps!iOGftrt%S zbcnNB&HrrX9=XqtcWvQaYIX0|hs<B~|CdNk+mRMyub?c!Gk;d{{X3_El6>#S?7!>q zL?ys@o>CQ`!lV~zO|!dBus?lrTu!k5vfJ}1jv-;i&NE*WW~f)hTrchvn`wCUXw>@3 z-}URJUpyTwWwr0lYL5x-RVTM`#2B!2Iwh|th~>1kW9?>?FVa?NIB#@JB522|ZC7u6 zToD+)czak?!}&bbhn%+#Z2TMdE;(|W!OVzx`(Aeo?U=HeomujK^0j6UKb~dzRqbCH zHtAH_EU1i|k&%7n*eQk0kKLD<i*N=UPI|IV-|XJX`nLP;FR-Py8dWzar<r+LKHm3m z?FFIGl@IwiT^g+#+*l?ad0(6SyO5bTGxg&91(F+i-BbC6a}S42eBCqGh)XJRhRgEh zrMc5`Wu}!bbvHH&o+mPKW`=a+B$Gp&vnD1i+{$!v-SV4ins(dNGv+BTJl%DL_j3-j z!rzvAyEFCA?C-2+bF6&QnQ=k0X`Xrb(&prc{<Al#|1?-);mY3bZ)&_cXo1Sx_p`k; zgnq6u**IBX{cU~T#ML|}CM}rCbbap`?Xws9D>VL92s&!UyqR_Hv(b8mt9;)w+)^?Q z%qa7B72b2?Ftc>g+SDVjU5_3L`Q4yc+2k+Cdn7}ksyJ%@GexJhe)S9&6%;<oPFYi} zrjQgE_)X-ZM?uPRwevGu6#1VqyvTBy#LCf}t0Z)+qeXbq%yrMD_MO}GW6l0z1Mih4 z*T2ZW3|-^(v+h(;jAzum+;j1VXV|!^?pXS7$H{8F5VnBDPKUbw+r}GT+OzL@OwT>8 z_+7Fu!@AyjAIe>MwdU73sSjMQ>#uLJHgUR{FzZ*f*80mWAIoj0n+SNBzI<+U`_8i! zxs5BY&9k^Z@57dFAt#ulckY{J@O#aZCf9&zDQj2_kL*!5*wd7(`pfOiR^1y+LE%|v z%TMiN-u9cXV}JR5#jStDRM%;n=ui8&H??hxcGskL()XeblLGI^ZRlZ1SMt2acX-Fr z`X!O`IwIZL+1ReGW+|8c+|&7Oqgn3qxr$OJY7ThHJzLK7Nx|y9%mir$k)&g?%anu? zpDlj%Lg3+zC7vB3XRSpAy9~H}mUpm~2sBNNDc9PV?0;=%unTiuKa19ZkR)ZxnOl{W zq$XcJF{$zP%7z@Bn%lt+$|gd#?7<%^9;QABwy9^1W|naAc^nhPu5Ei!b5Uc3Z&c97 zod+`}t0{f%-M)5)+y`UNvV$}CeU-HllHS`IY{MKZ<}vfg%ghQDqgx^#eHC|D8CCbC zO^9L)72=zrsJ;8%=3A$t&oA|JdYf0WcH$m034bl^kiO|IrcuFt%_nM7HKOkoX`TC( zb0pqiuJh7*+bzqg{bw1x{}DG~{%)J*-A^)48`Vwd$&tQk+55VDmf7t&Io?yRtiQQX zD|LnRQ|_4+zI!LOEs>v4bfETfb*lHt>dRGI+>N#Vdi?6M-Y6{Sy1dtVqwJ!KpZIPs zN-aN|fAVp}9*<k^J6?Eoy<7Y`pEFV~W{K_o7ovxrGOk}>w)s|l>dY%)B0_U-s~A~- z3vK@~vHq^c-RZo4_WB;<`aZ>+<Gb%y-~N>2>?Vs(vSm#yNz_tGaeXFo!DEwS*v%z} zpEEx4aH)1^SuClfF)MrFj=p2H`-L5p1D#w1nEYK;v^17@RJj~cmRxjV!D8<_X9Z;C zV^S@y3#juRJnJH2eB{St$@)bTQ=FYvy3gqHm@~b>Ptexg$i>rO?ffo}J>q@y$~Fe+ z{Rk~s7n}3v2GitIyPy7eeR#^+ts%VW^TXy>K3Dv-?xftS8;u<nUN2_fvldeHkFZ+M zn4N8N^eShC^;4~Ejl7?S_<hc=KHdJstTOr=+olJ$FG?bB+0T;gT)U($U3rQ}{fj&N z%94!k=aM#X1>Sr&`~EC3?RS!^8Pz-VWH^;REPC)t?%2Nve@|T5BARji%9@QUBlq{Y zzU!ZG{fX19!0Ng;i{JI!+0tj(lYgRhO{kSuPwnMuca6uEauy3DZ*DIW`n)J<X2-E^ zt?sU+&c#{+zxLbzefXdG-AN~KJEWv2b+RQV_w>eaR?swn1xlZ6Z}jPP%G*H=kzedr zp8aOz*vlcrKVk0Vo2-#1luyp)VM?ETpI<)osM74(d~Y+J=p8}kkvd}jdo53|?~ZZ# z<*ygF^#9RMZ>xWORjsXSJTLz1@9RxciF>q#AFS@l7AV@OvDfYKr#JF-f7YG(G#%0q z`8TWXrs}$jn%iq?^^8_ry|LlP)mL90{5!u|Ac||@yO)d2-hay9tvUaH*IT91UrKT> z+r8!Iv)#RP{NQGzLgPujf&Fij9&VrVX7RTA+P}ApCYEhg+o*k5DwKt9is+#gXP2zs zR<~DU=1Q&2o)hP*HK_$|>6~=+>rBtpk-_%Q*y@w#Sp@Ia-5eOV%ZSrcjqS5u@2SI= zrP=SlZ*`RXs+BCbI*jq>?yXBtRkUA8nWZ_ewENM#`C3)8>RNT|z8atLGTLS?wb;?% z=X$;D*DBIWr*}`w=9uJ{9xV2g_wFsm%&BXCJ3U?>Z)_1Z*J0}|foc2eU)KNsKHXNs zC(6RYTVfS=y|;Uh=pq(J+2+`P*BhRPufO`K)=*95qHEQs_fO?Hn|M#l7ne@To*Kq$ zC#J*9d~jLgk*#`*>Rm-93Y!-CZg`Wqsw896oP(R0L=+>Q<UK6$l~NHn$lKriCqX)B z&BeAnX(RdSO^gL^%1&ElBw2pE?l(>FdZB5R`|Yc1MfCoP1=i0r30B)3dr0&{R?cCG zKbPuO3+=qRv-OT}$n_LgvBD&iJoO_F&uE=FQS|6I<7`(!xg%j)IW`tVxTM)`NWJK$ z;5~KcIh`9nzg<ea6@Io)QFm{+@7erKcMZd*XwG@gJabcNda33Up<bWJ5Hq7yvyPj+ z-@dqcLHZ=d0G_vk2lE6z)X!Yb@*z8QzueDdjmo-rGS<vbIcuG|^YH_|=2jPm3nCk4 zSUnO+$cdTDb6AgyY2M6r^>1ysgBfq81hI+nuJ|6Dd-PjVxnk6o;#)7--)-$PxcWTv z*}sHRzIO+&Jjmb<I{)vO)p^(IcG=p;fB1KJwg_=7IUhgi<9CgH>hqFIpSGT>Z?0C1 z*t=-Eg{thmD>Ab2m*?!dZ=LDXuzlunAM0o4Oc_moq}q4r79BSb={cS@VWzafl>OO1 z?tQCt-YU-QlVDw^e0|RLMe8%RvTQd?%@O3X(VS=7ynGJ_tH=gk0j0GK$5)?@zRY&* z$7-4N4aK}Mx~l?pv_&=hc6j*z`Fdw&_@DZHh0hM{&C+H4>1?%1ZDL={RCV8r3uf?5 ze>VH{nQN2hMw)%HoObO%@%m#5-xM}KJ#*&GnN?2Ree7TNy)^S$`a}12-uiHTU8(&2 zn#ccs+FQh0utQ?vp=k34rPdjn3XbcR-gqFmb1Qo@^JR9)u8;ht2@2M1n@+zsZcX`n zW$Q{N#<Kdu8jJa~`3^K+<VlVC$esSDA$)bI;c~IMQorOU3`*;B{>jA!ck0+!>n&m2 z!+N4`rRUw!yYgwWk^u@k_I_mV+&l62m6?n4*K$v7@VU+!|8c$5uND*AyzMh&4G-+! zsr0n8b+MjN;o`(y8S^)>E@#u&_W9-6WjV`4)>Lj>ceCcCM!m;|9pM))Id*%Cg!UTy z7^IwhP!TYnH&AK~yNzj&=(OEsY>Ow&sZ>{B>blaj=2`WpU#lvA+!x*(xK@w-hSv!Z z^T1h|Sy6ww<g>I^8_%1>y5ZZBO<A)){TEro8)NXb!v3k}flcRnmt4&ZKC2U0Qy4u( z__<O0+d1#l`?j3;E$bdt&(CEm*T^QL>h{7xzGNHUfrskhYhSUSlem#v#lFNOFI}Z( z3cta7?;NqIJ9~4=7`<wfW~68NtIDo?xb8IXwgy(m<*!-OFV3jhJaw6K%h&8vt-G~Y zLyyKCs9c<L=+)J}qHj;nJPJ$ij$HEcNsHl&#*(SI|IQ>l%obOi8{VlA=2@?G@U*n~ zfhkIR{}i~&bX?x@VA~eU_=?Nz&zdBEW@SVu@XnM!vsj?k@9F15&6#`lExfL^PfFqX z*~FTtnKh=HGgg~v>m0jdz$eV_oB8!(eM8yHDZg$fxV^o!KX!N3DJdS0Ct=k>2mfAs zW$JSJ@x|A7FV4Td|Bcw*4VOPXa@}Qqm%rY}hV`;d)pIck&%=jgkB4sXTRcm4eZ;mm zPx{U%%sqE|)=jyMX)kv*bT9fQboi>L`JJ^=Rb0!?ubgS~N?%I8duG9lyYs3q30*SR zTFtiU(F*_f>D^Il)vlkY5;=3{f?%IdP|J#P2d20CB%X$?Y|n^WsrTu{6V{_HMGmni z{yBEWzODCu^W;#Js%X#M7nkqxZ8)bSGcEC`JWt^TPlvd(ul0lcxZFN`F??qG<IpyX zN#U!rN{%IR?fCi7-Jb1%=0x9iUbctQY3&a*y_od{ukp{g$9^;E>ohfGjxDDbdgQ4r zo#-WZLu!8igOgHxs_tL&R_*@5cGvdLp`7!{=Ysk+HM4H!u1`H$#i28Aui#emH}kd^ zml-bb;oKl0!}j)o+`B7$fu^_DCT6N^Z!+;MR19O@lz4fS$eLL9*|XMe<%v4yf6{cC zS5>%C&sn*TS7K(Co1R*+jw!{eSM88arQvRlt1ou_{G{Y`;r$HfLX%!8>#De{_NP`Q zTddl?rPcKq^wz!1jt{Okm@WOJsZi|~bC;mDYOMO584XI>>VaEMDPOg^+_x_^?B~-* zlP|_bK3Y0M%I&{WQ^%~g&+p7I@85Hn&Aa4Hfa^C-fg)M!c}-V~eENRg&WQYFJt2={ zmCn2CIWG_GSZDoA;_D2Tt(TK;`OCU{{Y>b4a?Y!n`Sya}i{~uc_#-YuYGQq~)E46> zT9pTO1kFm`^{>jdGhBJzRloYFv!b3nDLTUVsq@UqFLj%x{;sxqBcyqC=cRQCt2bV+ z%uZAi=H`FVajCg){jLYmliijJ1+y+S*cdCm(sW{N>eCZuJJxZ`4tJa!XY=V#5o3w6 zMx2CFN>;6p;N@AGSFX({nRG8x;HUQIs`{*$7Kf@0D&FT4gWs(c6Kzf2>Uk~rX-d|` zMT`ZPg6u?;*E@anyHOvY|MB6AfLGGTgG8my_ceV@^6>fI>F&0EmS@TGx8=3Y@_aRG zzB@O>$#0k%-P7NzJjY~le!k9;YLm6=V%*++kAJr5E^p(fqpF-`lm7fZUs!gb)t1>_ z_sYTgk68=b!(3}WRW`+BCixxIKf%yi=3wxpwV^FS?vKi(yvDYl)Ar0}e7$C>^@ejB z&hKmIuX$Pf>o3pM2Zy!<zf{S;&hS+1;jvo5Y?~8?TUW}?-?v@zQ}O1>)?Z5w&G0qT zPbtU}-^o%fqTAYhTV=z)b(&#G%T6-oO|e#Y4h-DzxwF3iny>on%-wqL_o$~;UijiD zo&TY1$8N>?c^l1l-ZITt@!!W;GtGXJ;<3+lJ)G=+XBe%yx9R7UThW%&(!Ij-84{N@ z_4Vj&4-gkwSa-27{X<){pXaSzp1bvbJezTPSL`V<zJ%NN4E~iFm!F+y<t=+=)~vgg zTl!~jyCutV=A!Qv@%nj}B<iI$7zCFZi`G6a?~5_L^C3+~@ah?+aEs1K^Ex@7EqZ&+ zB5vllC5_z|mez}WdoxL2;nza7x@X#rqTS1vcl94T^mDSgRpi-4$NC<7cJVb#ejUK5 zS9&;fR*IM3T^5$t*IFBd*`m&B$Xw~MdvbfjnoAq*IKF*nFLUJdbMwnd^~Q#5C69cz zRjI#jEMK~Lr9gOC$MdOb_bxoXH}90Zpl{@~;5BpoFU|ZlEq*=k{Nr<~Und`(IpcDP zp}NlA#YS7}3YWEKw4YV^ux46*$D>ozUTZ1)_*~c5O|B?PcoP@lKFcCz@yZLb?=8IK zS13r{Tf4qWF6&Ciy!M;U?bS_ul_JmT{WCvX96bMr*G4p9MtxeXa)9wBm$*mvQY%j6 z2{G2`WXqJy=Ce(Ds#nhFwE4mNirouuzvFbb6wJR7pC5K^&cqXQKOK7$GspJOy4UL- z?l!Dyo@vaxDqn5h8A-mH^z?F<xo;Pyib}NJPGGqC)GYYl$;&IIovYcibzxew8{c}x z>o4o&mpyOIJeh60!}RmYZ<kzU*RG#zy`^?tM=Q(S&<k5{-fg+CM=AZoy;e(${^BQo zD`uGGg&5ATQJH_>^qKqjUON5gYYm^9ICshI{hE#1VkzrByxJJ{`D@w=y9tl3tlt#9 zv_ZL)X~L51G_{?{YcEx=oprBF{b=V3^QVt~aaUb_t$$ZPdlGZ<U)x;w(!{dYt>I;f z>}O^6PK>wQJ9+n9<I{WBY__&d^)3JN`||vM{6e=(J0=@)3NxB)w^e1C#KmM{F}=ru zY1`yRA@k{#j!fb5CdLW|3PE;u`T>b0C8<SuHa7YJh6+X|W)R`&AI%u$+IgLrw(~kM zm+oOS1WyaG7+M;dPG9|x*}nesnT%g%FFl{uS9PiV55N3;edQO$|N9=iXYSwcyE#~| zZ5N~bS0nYM%O`LA{K)C%-^c6YduP}EOaEo@_lsu#pDky+4HAESnNec2`EzF1=Ixb_ zzCDlBTs_rj`yb=!5~ps&`{>kPeYzyKF5tTj$4~Lki?aW*&RH$3@7Hbmtj@AhCc9p@ z?%Aj7%kM9n{B3jj%-Fflj_RCf{ODy_T=?_i;m0RmI9=V*9hPb)TrzXf`6+u^EvNf` z=Bs<xl`XgBa+`PJ;(a`&!V}N%OpCS(p7z|Faen?($GtZq^+VTOQ+f3MR^O`E%z>9H zmscGSy}I^{)V#BcLyn~1D+r&qasMZ&?k3IpT-*BFX*VUT=dPI-d}+<x?vF?Eqvq}v zSX=cqBJ<Cul@iBuHV2-yTl(@l|Lc$Y<F~mz{B%&WVw<8x1bf;Vp;X~5-k&blpZ)Mi zX6?*<e&?3z>}pN_H}BsGwHrAzIbHbK=KSAPdGh%$lj^r!Z_V-*KHXb&W{UFRzJwdm zyDGHjhX39Y5`Dh@yk1(Rl>Fz|$eDtyRjiG_6mq*-vjaCqzWLR=#K1!3iL+0ATd%lp z`i6@V28~wVx0(Iu{kh1^bARetr#Dk2pC8zB|E>5L*Cz9$hBAd-$M3BESvJdFz$Eg} zqO`N$CP(u4N80RoxVZ1t>6{lky6!v8*f6`}qfbGo7F&eSX1O%u<}}q$jq@*7`k2jb zscts^StKCM>V3pd*mVD{lh+^KIle?ox>fDxi<2>)^=>n`KgEYHSl2U8eC{TxO=&#s z{QTxliZ3?EoDjPgcJ@c<k%P76B9qzfzFc9YsLa}$_p~r~!WZ^k*|)pi6-<v{WHG6q zIq}MJZ-HeFPSXyGFU$~}=_&kYwatXBbLJE$NI$&e61dl}<#XDXgv#G>+t|+TaNDH) ze!~{#=`5#?q`JP5-Tcmwv%xv^>e`i8BbP}{5EuNtr$9Br<=@{0k{9-R>^GRLm$S06 zc;f8eXYQy<3RIpBjuo+8<=4DIqI`|S*EsV&C*gXXG~N4QvVluBs6^bW7T1cBl3CRA z^xd9I>#m%y;!L^Lvn<Nzj>O8mqSzL-<_MOq3u}zI6s8zo-zU;3YT|Xg^G>u$(S_7$ zb6A(FYn-&P7j?AFxo>&#mvqqvne~Qpq4%0CvR*Ho_wA`?{~yWK=Vbj=I;wgq^^`WO zX!6K?@t3!+zIt+Jhih~Bjg2}sRmV3Kyyu?AT5{{@j8AQ6wLF>5UtL%6ea7*L{O^q3 z-0dz4XkO;EHO%pV$P15!3$^Xk7oB}@diTGof^Fgo7uP?Gcxu8UkhECl=?m3t4!^b| zA9nFnsP0?)lkr^Hqcg7_@*WIHUvuQt%<ognlrq(?z32$MJ-7Nx{l-I`Tt#UjmHQ5^ z(7hF0BzCCzCg;{PnXF##^P7TWKk8WT)DX_TyXld5M?gW$)6nzM9?z?9?_aF7qJ}f2 z)zNfKqLniH?iGBNEJ~l6da7>rJU#StcW+*ft@T2S(w#Q#ol`wUe)DQ?(Rxr-%jjm? zQr*xxC56|m!Yfv}KyRLietp=7^S#U|i(RXiobhMYJUCA@jd5$$F+TP5OAe-`&kc86 z(x0(DK2CJvaRH}S8D>1}xq0S2oGVT1ZpCT}eR(OQCZqLOZ&gg*NuH^SFV{{I49}I( zyu9Mmw@t|gSI#LO4-hL0RbPKEwPO1-WA}uMH`=0Hw>?ba%1*lcMuF!IgJNR+D}y;l zIqn_4({^>iDeo5b`G%XCDkg5vOI^>N5qD}Hv-skJA8Hp(T=4rqV8KL>Nf9pF5^4k9 z{(P3aRO+IphvbrqdyZWF&$^kKUOf42WwqD4cIy$}hc^4(Hg-QcseO#|mCYH~*?g5s zX3IPp)9<m&UKz{6$8l+K%IUPJZ#$<Hyfdvgw_9|kMt`?yj+?N@#}#ew7tbiW;jzbi z^YnM|t0xxACu^<S!?)_)-vB4a_n%j_mrdo>irinsvVGCI1$F`A;lcM+UD~#7;QsuE zZ*Fe)`+KXD&xy%dahln37A<=c=fBc*=Du&?v$DhGX0Ge47dM@8$@+oAS6jW9Q*|9j zQoeRC)u`W|F=f&V-<gU>1O0=)MlAFzxn3ps^`r09jKYFxE<v(?mQPu-;^ip~x0izH zO4+6w-&l4Rz70AP9e8DG;-XX2TG-+oJPx;CJAC-8+q#LVM^@PcpXT0CKDpRm?WJ9B zNuthlwQc;}jPHW94bp^W?eY@wxDzc~^7G1frkWiqo@*Sks$cf$oX1+V&SQ-RTmL(C z|GnAQwxV}&;=SztH!Ht(idlYs^w%PSLGrrO{`l6yU2U~%znlC@d+^Q9Y{uQ!Yptqo zCj9YR9eVq7n#nZN)7~v1+8a8`Z%<(635r}|X7%_~=KI^PBg9W%*7rH@{pIcR?#AfY z@CP<s2^VI_*6+%WKD*+t<>~p2Hf04C`dvv!Pk)mM*jljmwWD41wRnLVR?oaEnpb|k zP`>v2PJ`4u{kng-_doYtul@6|{!HN&i|I~mETQ$LhA6{!Z)0!gscaXlJs<wTu(E)^ zTjw6r;RA6sf)jdKPxdkEd{KDccirpSC8t@uX7%%nEiPZSef2TdS8AiK!`gRglWq2d zh^{`Nq%M`+b#X#(ho(?(#UE8up`MiYJu1qkdnD4=n)(%-KWNc^yrtacSi|%d`}+C4 z+Q+P2KK=M?_Hh2kl@X7dKU%u(kqBLXO!#W#;-oDpELFlL8ce<#9_vrK%r~~1wL-&R zK-hT;TYzDfiQ<eI0d^{#oew+a3kkX3;r^#s=QelI;aFXx!?tNDZPkt`OY}8tCLL?? zVVt-oV)9&1rVBmg?LL89o=nv5nzUub$)ii^JH9zg4RTuAHDd~6)P!|Qa=OfKiro>s zy0DJb(^jinDb;l$!(3(~=P7e8oL~)zym&6-c1(c7#*P$5<!g%J-<rRk*mf-NN%Uid zM240K1$C3@5os&`Oqr0Lq?LBihvn*_yNbIEmbpiFU3qgs@sQkhL5ZM^0ogW&uV=~r zzAq-kqEXNA(vxe2*rmn7ttTEaAMRKzxzpmANFevhj?VX{=hyf+9+~vR)KGm(vqfBv z4&zsY<eHB)$;Z3X@63`vdGYEWo6l>`hIt1n9cBtnU8W#u9j!UDu#@kSOUyKexd)XV zbEY)zP)G}W=2tXvR(fJifJzd}ku<HTJuy=YZKHjDF4JMJkM?Bt@Muc4_?*n0oyN)Z z>GGjzH;yInHRh+tan=+oursbyyz{9((ZVCZK~E^jMdG!1P@=?Tv2R=&5@*j$I(A~m zn^vjo8?Sn(UToG<H407W7cz9)rheOAZJLdtTiUvl#(B@KOHH?Rw(^?F=Baryym5j= z^HXnAwu{2|m8$Kd>)V=VEPWQ@%{9--qI%V_6}gA%PW7pt3cZwath;9OpP>65!8x<% z+z?OcoD=!_P>6`s1fM+~kC(<R2`o_1`(`7#Bw&R{;;I9?#JV<}kl{*B`Ov#z+0qF{ z-cySlibYDN)i(a$A(Pene}~N{rCdwtmj9ySw?*FXP*DEiCplAM_DZk%Gzpi*C%(!0 z_Z0<e_?^7*qp3ScBEF#Y$oeT8HeS)+VRV`6u&3{jqyvVh)^i+ZUv~6qj4wCO@q6mw zjNLD$+|e^yn_Qu!w{*wpHL1M%YgE0T<vz4FsL_?SxU%r`Px+sXjz8re&RfJ&SfF}M zs`gDy+P?=pA`X)LKSS+`9(XTbB2|Cn*ko-}vw2f=5AKkfq3ykF!n+^mwKwuk(LAy6 zM^pEr+l_qzrrS7w-C+GRpW9UC%KHQSdK1=Z91}eEsa5J+)MgvSjenJbQ~$B=SF|xc zV>ssu!@0xK(b6_e=GP30&IB)VZ)4xaX2GAI(|aOBGDE&@ht>aYpPpX*y84&N-nx(X z&FixthxzBl-M=$$|IV6?akjDlpT9c#>&@lktCySKkBy79Uw&j;^VSJP;vE&mzd!LM zZVpJ+51YSdZ{5b7yLP&rdz^gsmi*b&o}%x6>T1emuG+DeOCFru<6m$7>7x7liE~`H z+nfK?ySVp>`0C^4`Rgyr=qapYeY^Bmz@wwD9={eh*RQ{R=ER$I-Tih^wSV$9@2mUy zXZGpq<=Mgil|>#d-)^pd?)vQKt1mB~zh+;={@ZK(_U*3OTDNJh?Y(<`0^j}j|D9)Z zZ`%8bf5e3j+5CLJRK5S(S!RE`FHe~L|6Q5)=lS#a|Ji?&4j-y#kLIhKuh_kkyVvN& z-_IhGXFvC3URAklOZKe#lwZty9@e**Go3u*c6akJi4FggmU*0RpMS*i^a=YD7Mqp! zwzK@1cg23<YrdfWGyW7iZ<toBX#MS{;JTZ4_T?Tnx$<8A#k1|+=0*o5E!_K6`!X-@ z`X3G!`(qESEX>^V|5nT6p6&cCpY7&;{~Y{Z{<OAz2lu)^3V%x%JDSE-Uw)8Ze{h$4 z)5`j-hnG#CpDrMi@+m>)Uz}RbtO<wr`pCWc%dS`Z;u5>w-;aV;QuphgxlX$HEm(h2 z@2+n<#TMt5hzK!NzqtJQ?9;TpR`n?>JaYHXO|^0f5!q)Kov#-km#61@BKSe%g2_kz zeT^%dKR?g<&u=@wi0}LE?)ll+!rmIAHYc>cVVzWv^iv+Q%#z#3o%Z+s3b8eR75w$? zjqv1)%h-cUE`0UoJz8wAF=|&vsK&a?H?REj)@7$WSe_e_pLDbKPo(UY^|L&q7r5SZ zTa$2!PkZK<xQMN~^0D#vbHbkI@4xrqZGQOm|7^$a?~A$L*YtGr)swGI7T?@wv#2Jv zw(9fIv#;u3&cCm)UUA2MhVMLUSr2RS?)-J+N8>(0$4?0_e<yu>m!y0}{C?cNn``pf zZ$1{2+$4HoB~O;E&nY9818tAk<YSd@>@J<Ur1?z4(PsyLCOLg^F5T_fnk1^T$hmN3 zVe8#RhwOM3R)ssv;<gBWEU?FH-3IHh#4kFsTnX<k9%SEkJE7h?lkd&60}?w{O?Kd8 zj&*7}A&{KW_eT80tiKN?F-VrDZ_#L){rHuTd7I+pmx`NuuV!>PYb`X){rP*#Qaj#N z32GN27JAQ3GYHR4`{bVC{kkN?%~^X1$Fbo2gE4DoynOfey733gJ>ORpGz2|35Rzqc z%lhFuriV{!<tw%~d{@cYQs2bhu32$=dPm;*+6RAy?9@JpXRlXa7Ew<95Wf7@YauzN zqpBuMw`A_TRi1TX>NA(^Q?&0{7f8<SInc}y#;<l+?rNEP#yM|>Voob*=Cp%%U3+3` zFHBc??#0yXcv9`Dd*6J?#o;fQ_p4Y=f9ue%yWS|G%HNSG^JVkCB^K=qZ;RDWkU65| zX4F3McAKBC{InNR2jWz|*erQfFV_BI34cw@A?tws%^kf0MkUKXvo6kBerq|W+}5S{ z7dvcw8mpc1i#4cz%PrZuHG3VVYwUX^68CbI#*3D3&Ub>QFPOK&Agsotc`<XYBWIHM ztncnedL7n??Yfw}WUbuIle|aU?LK%{D%W>4#s*d&c=qZ7kLfo4A3?V|yqRq?MBjh8 zFTy`jI{C9-yjBs@W|c!pUo;%QT+NsLuD561XTSS~(riWRHaMI~OgPJ0628m%{Vka? ziMgdy%dLVA%douupVIt&g`u7CE_LS}Gi3gCsC~ThC(*IY&|&i8#YIi~j`QklQD4&S zB-!_+zIKUh;Ph#lF_SwtC@;8YY8L-x-%p`~In(kU91PdB^OBq;y`t)jksG7aLorp^ z1;GmxB)&Um^wk_OVN)uv-ZAIEhl?yxLhEYnUk8esITU+_+?~7nE1!W%5`U;u|Emp* zv%0kf)>Ulp6P{_H(qxkV?P-ME0@EiG5;q@dlrCV><*a`h^f5WPas$`Drc3X*&V+5T z_Tii#EB_;Pw{}+Sj>Lrv{u)lx&gJy2eK39T&O;vjbLKKkU#g<f_cY=9p4<ICr&+>F z)!9Y+cDGI`b2-)fG%CX9w8aVIT|NmiinCXSZ-{)YxxXMM>X=)B#P*L1ST3lCEZE)l z?0~z^=Q-?oeIXJ@>oW@Yy-r`M`lo#_KSTcQf;-z6Z#(By$us{|lr;NC59i`{jY?-k zx3Hx*7_>~iQ)OhStEIC*C9l3^*(4pUHw>zcyuRU&xHl{C766iRte=XXl}Dws3mO z(}dQgJLc9t^fQ)7JXyNI?(#*(%dR|At^^468049HPguW{|JOuI`5OuKI}3itw=g^v zv&d*Ydv!`l%AOK~`W@{vd#1ixwx8$NtHAfkhgEa`y^7-1Jk)!P&n@ER!evVyFVpu~ za6opd!2ONyKD1oB<Kw%t=5t!@-%qdZet2P5?A?+VQLc3CV6l8oQwRS}(|3<q*Re<5 z)ZOU*Zb@iHXQ1_rx_X`F_p?`r=jYd7Xa95bt7aBkjQk_FnyjSTrP?!?&xSdEKVY@m z@Qd;<+cyo&BF&GM8h&`a`|Ru0yU$+zs=Xrla>iRl+u8?Ok{miMyNV{P<ZznhmZ^Hi ze0u!;IR3I@7tXyrFj>kX@+|M?bGH+%oAu6GRl3YQ8KCjg=KG<gM@&76ra$FN&G49X z>TrEaS2zEpr{atDRn>mJ9=`s)T}4Tik@Q|KS(BYN`Yv*%oO2XWec-2>85wKeDOx1L z&$m!!aYW&%pW2NFXG?cY)y<k2F!l7^I;XYIVqZ0`+Puq6GRSs$T07TE*YMrjRwQ4y zv;13hajT52hW$$MWfN9cJ6r#Zd~F>m6LZslW60~d^~=~y^W(Q&tIpYWDc&*Nwp(iI zo9_!$9%VT5U$_|*%*Wt-c0t_d55hfS_kQlWH*Lw5BU5gk(dRj?l*saQm6ZRt7mQY~ zgAX#i`7C!OQRdt__7$Hu2hQ*ls^jNM@)MDibvWGEvA{#Z(V0ctTl_+af})0?+Xa@z zeV6TJ9ny7}O_l2lpQW;MnJ%7kiTTRu9)H6*Q*BRIWK1tP^Mb)j_&|wd$oBWadOgRk zEcH*1J+(K;V6UT%^R%^`a@vO1cb@SJGH~i{723%DG)utWFPs0>o1O_eKeqU+kbFG% zb<owFB_|BZQp3V~)-^V5uwC`tIH-x&pt0(s#O*EIFRrYeXBOTkT(8B`YWLPUU5a1w zeDF8701h|rTUlLJS-R@a)4kUo%?(Xj_bJfrM-NkG@`LpAeJi|QgwFE{+uPn!e0f69 z%B_5Lzn=bNntA#D(s_KY`kH=+Z!g?&$K55+-79nVwp3a3*GX$u>vgJ@3kArTwLSjz zl0%bW@*U=oWsb|&MP1^~abc~`bl<h2#DUkCt*PpLdTHnj{n}Mlvkf^fyt&L{5)`dt z8!%Vq%;gkL;jA*|{^Hk-R+Ep$%;m9KIL+q6f_-u;gs*Gxe43$_6!b8!LGQ?u<%VIc zuak^J{L4@2EePJ8^_6dahv!spOIw5GiS38Gb%iwxpH6LG{LNwRlSyuR@jgqI)R#Pb zeR$2Q=|S?p_N`@qb!!)A<K;QqW(NQI+Zg-8$YuQ%LD4J&{?50JR>fYe9UUp>1Vz1) z7V4J;Un$J7wYIF^l=EzYW&Q52Eoza)=Uw^ss;Q(rNZb=Qb@TQB|Cf%3zs9{|@Yw5S zrJe9#i~Con<@qlgt;CNUvgEK|c&K3g<6QRoD+MbS6s?dB%-p$KZo%fbv$r$7-XB~U zb8ju5(Y~tf<+DF3Y8EQr;PCo&R_5~U=5r#Pv)Sfqo#NG=n4DXDW~bD{KL<8Pe_s%{ zkoEh4IT!U>navmX%$RV{hIbe93b~9-6N7IHL=Fa8HJl6K(8}zLv1$rpn(TeM`BY=y zG3JKv3pUhWs!Xe4zi=}nFZQdKtYhYR*Q%c;>~ncnbMX}lztyt+mcxJ2V1E6&$$}<} zVr<Q<56-*2Yk_s?-DmT6z7PApqNJ$f>f#l)uX0?)GIsm=%#+^95N5H9>u%O8y_6F^ zx2i7)mR=N``uo_ccYB|$cquStUHi;+S9VrixZ=FWyK&x~?3nuCbMJR^h7^}Rd(U7s z|4{R~`KEp=0@>wi>ll7*cyQNE_unUZb$Ndu9itD=O^R+@lt{mEqhw=Zx$xH~C#QGU zzWuWG%G-Ba4%GggGe?ux(BATI$wQ^1wr>_*QGWWm?1;nh^8(ciiyF5USV{{iU+AA* zz0u2%k-2_@?sJ3l^Ug)xP_37EBbpZ--P>7hYklS0pVgTi?AP5^Up1fm`S6SDi%R4d z91b{L_xH`}n>vht%N}>^%)hs6hIRgmoz>4@@P<DB=5(XlxiC+^!>(h^+7ng#tSUb} z`S|qiFV$GSh!@?}>>EF%OGm8!z1%XlKZB=JZ}*+^F6CZ#^wZj<V`Z$HGwQ{J>f6^< ztKXOtmuL3+_Un)P4p)`!+_|^vyG&QowGjW-UkTs;IBJycRBh^X-Y0Q7qCO#M{^#m% zhVv`Gtp7WY@BiZ-clJ+{?35Ut>sgf9&v6x=wyB?f^6;0fm%QVudL`IWo0g{gy<)8X z_iT0ZNw<G{E_STi$s^J7yXo5Bl?IlJTOGsdU%gk@=*)Xa;bhHvv1e)pPx*`9CcLyf zRP1-_&^y(w)-(2Rm|&}HApV!RT*GYr`<SDN*FP;k?-p{_>cjMTw-%rKU3R4~VW#Up zgK0YyKW<o)HS?%n=-N0Dvv+}e`%9Tis;@8`J=-Tb&-2J+?;pwGPaZUse+bBV_G#aX zFz&xi`!%-vJBQxd_sD9&y!p0OW|uEb+`Hb(Z{g3AZ&|<EEVFp>sCr?MIOE*@gef<7 z)gQn9dE1r?_A63j&(E{9iha}Ym{a!8Nj<+cm-f!JyKv->{GRE(mq7C>Oy-u;8|QJV z)*G3ltPS5QeX>t``^S0fFYtGyiIzKDQ1r-AFjs#s_GH1HnIBc%9>je5tr)4Yd&id* zOD8seXYt)W>uA#3==pWl2R)C~7ZgeSKYjUo{N=3yAGaCYoB!`uaQ)x;@sCP>{Ry$# zKc~)`Q+MlT$$+22O&^5((nFLoX4fzH_u=vS75??VzQ25aS$_Xf?P4yah%L+W<<&oE zZ273J-qU4%?_b@-pvEab#Oy0d&0=S3@A{&<K0V+g|4%vnoIg8$T+pq{**RT2&U8ci zhrd;3Po)}aKm6*?ueY3gkYlm(t_Q9M0t_6KmmXW)S1<MJbI*hYi}p47lq);PD$e3K zS+B8c`Qtpd%kKA28OxV0-E_Uh?cc>`uA(U}26Kawt8D%yoxYzh>B!;ba3Ur2g?8Qc z+|_^fti7PHZi;+~%g<@=l6Fh4uThf!w|PyMiP`pVJz6cE_qE=yRmu&RdycE+`-L6F zuE8I_-o3{gX1D9K!M{Im*M;~xEqr|J-YhleErvJR>Lb{0xdgE0`~APma-MVfhcB-^ zv*kU-Z_TZr`%ldE!@eH*Lt+6e%vPrhZr;#JY+8L|)gg|*3X&2JI`$tHEV$C9qt`e& zPE#oAO5`OjX#;`Ti#D`wQL#&?nf9t^`-!)UFU_qp7gpE$XY}Yjv%1fQLyh-8l=J*Q zv7jmHiqTOKz9#>A`;4I3$r4vxBW)k8T%r?Rnk)2ki(c&W_x+pHFPhvnl<~S0f5>35 ztA+iE^SyEsy*(2X6*zV!?W{4YEMM7mi$i+rqV}MWcT;az>28^I<GS~)*qo^>ii_J{ zE<E8D-s8rs^Kk)}b7K6?B%u$1c4qhOCa*R8`MtON=bv>S_@yOk=G6;nJ(9cQ!s%<Y z^iQppOReywSBy^6jzwEu{@z>u^uUt^kMx*ww;Y<#lbHOSTVl<e4G)waRGg3cePp8! z#{~-~jc01Fm%N>#U2XeR?~LWD@J{Unk?_UK9@}WFPnhDd=H0@K(12%UmoJo;ZJuTQ zJty$<!3(14mCEUvIqBu|ub+LPSihL7C&O&{;=UOZGkVS(`L|@|sYiz{YScGupZhcP zU*TWf+a|IZ=5Y$|1AnIQh_(qo=lHasB;&%No1GbV?rdS6W4y3t;dODA-4z#A9*A8D zzNW4(qwwXL`sSV|mMfQjUCi>O&wSS`nJ)3;(X3X*CYmf2FLsu(El;bT_ji-7g-T3G zy?n)HgI#ON=kmPz{_C)}O<V}4_FIn??kp^8MUTA{WSN{^U=ipRt|iZPjpNYbiGmaP zy>Gl<Us8GYP}S+0b9U1^R+?>-;7VBB*}d+xZ(WD?zB2tJmn{d?@&vbcnVmW$tn_|P zgGlKY0lTenZlRx_|CN~Ga&kw`rCBd`-3>kJa4EE2IePQA#@Xh%mc3agb}Pg+Zf`GQ zb=~dH)_y18<3|0*-e(*ZAKtDUDIX{xdQ{^9%c8XXzl4KqG(NH{vskQiNK37$Ax+>R z$F;m^Z+t%0=kB@l=gJI2{&{Z?udAuC3_7=7YyG5;M{ZVJTRzWh;+A_qip+&KEPXO( zPRqaMn-3R1(eJL;%D=s>OWASNm2Evfvz?k=O%dW2bm@3leCEv}0n^<9{ry=s8jY(L zy6+C|k5kdRqM>&@%eY|@C*RB@H*=BsQx?8#DvLUPN<p+&@?9a{17WrKFAP00SNn+` zdhWn`wMTbz^t^L-!VHB%7<MFI4bwRB{ZSy7@$w_f<h1lXY9=Nwsh`i|f5G|J#Y+MS z32A2H>a)U)ORe|2J=iOCpQ&6gdCJnM+crH})IT>N_*2wzX1(v$Pfk8Ji~SR}{qDs2 zo0sx8A1ctb>zrQnBx$Xi#Al`Ji<394n)IkSV8_!{bFSS{N!vWvJbabJhqniG%1k_` z2(G_+DDvA?i2%o~LAfgxOb;4~)VoYQp0Xf9^=%GEjo$8KqMynXT3kdjzEp%Qi`_NN z`%{^0d&JqQ?swaTOA^!Vey{qJ_C;XRCA(G8<pD+6k;h(~4tjPV?6%XVN$Xc_{3f2J zt1|769p|jqbIZ4@ev3KzQzG}Nn$V7a8afOdGCBtONfEc6NS|_*IgymIS#(#faoDc< zypxN!m>U(b`u)Gr^135J_wj75gv&Pj?cQ|V|K=H7(zAm7Nph<5ragC8&CHy)@FFi~ zQQwWFk)g*9H`nP%Gjg#sd}In-7wKlr)4zAo6w#omid{BrjVCV8PYE(H*K|L_d1US# zj@~{^{aThy6VFaIdcmE{#C_WC$IIE)s$4uaM)kaE=SAb)bW*n*U=ZG__32L7Y+Z-b z5_e5sw5WaNmrOJD^vYa#*lO;BdyBthJm7kE$=&?yiLy+NlVTy^i;NaY#xDswC}_^~ zK<VkE(^tG2)~-FPcO%owK>5Jpcbkn5vDmiVefDaB*vmDRt3JO>+-v6Y<?xjK5!P+y zTW)`RG1II@qdsa$mP1m@=htt;f6QK3<|(~8?{0G6W*w=LErPF#W$*HD_V!w=@53_b z&VDcLUh!+suB{di`ni6lLH?pyHw7d3T8s~!D6Q4se(m8Q(LGW>-e@oMx6%}x+mxkT ze&gN>G4W3Z>Yq1?c+GG7U2s*|X3pN18w3okrrcZQ{q)?5x!Myg>!X!(B)D~VGJ5ac z!q_y;!SIpz8lJdaWsNtxKbM$ZYtuS1mqEnB_Tcw7{Rf{H8~GY~^a@4l9{7^l>2>w( z^o^kbh1YBo7bx>AWty_q$WUEs#X_cxE58!9%HK0TkR9Fao|>0uqqphzVr9cr#nrrt z>6)F(zh^s(PErys%ihYfSGE3G8pn<0Sw2?J`0bg#tgR9;;x{v0|L(c-t-FRS5fAro z-MO(lpPOy(S?)u>a)00U>&))lEv~2L#q-r*ah{#)T>0;dZ<@)?pQ5}<?b$-^9u;1z zFLuYv(>(IS3JdG5FwMzqKOr@Zd)KNTKLS>KIyN`^{j$50FJ<b!PYP-}Y^AhTsNS22 zb(b60ztDTe?VCQH-|ThP-ad`t*+HHIEQ((_a;)QhcY51Ia30s1)vfY7jAh5f``-lr zi9f3B*~EPLrY0BL%|j2E_p*sk^_VFb@@(qXJw`FFe_gv9u0MCziM;bhA)+1c6po+T z%>Cy`-hx?KkCttlb8G$VcXmqx4le(>>)fK01@(C|H&1=vd-%#t->8f~O4qkNoYyQ_ za>g@GtTcK1jJo)r8S3+|WT`(-4)^@#a=Cx4^DVWbKGidhetlQE($4hz)UrdC-+i3V za?0BtWoWE)$vPVD&Rxk;vVZZx+xus(S+?$`YS`;<;o^_3b}-)ic=BoIc~Sm3Vy6zt z9^dwQ@=BXyTk4l{Sc&GnjqR5S*pcDpKD~ycw8zIN{z%Pntu4DA1cxy$s$_b*W4`L= z*02K+IY*V`=Rc2}to7VlJM>y`dw}!xfc8|&PT$zdd+Qe*4epmypHO)^K$v@0-A$=4 z2VPxFE$yFo_flS*=Sjg;ce*r}y-9hqY26t!<J->ByEn3|mYU~Z?-^nid?)07nYGzj zgC<*pqYP;syAJBSoW<sKO!(kE78V~tnH%lG50-xO)LfG$Xc)XWhT;3NC4tk<o(N#x z@bA~o+zmkqA2t`WTB}`5sI=h!w(qThRvOcX9JNPZ)3+VIt$(9WA}lje(A_ghG?Byl z%rbe6S2^LoyF%q|R0O==5?G&TE>QJ*ZrTohmNEfl>$M*oy~>TVL@cjswY~b~mGE~u zk15ysn&|=yi%SDM<6iwJFTdb6k9A|k>9)G{uA*B0Tk<?47i^1kbqYUy>ec=C*JG?U z-Yc&;bMJBW^>3ETzp&hWkY%Tn&3x#V*D+hYJL?`yZd5#Q<M@r4GaiP0*qgucTfG<` z-#NB7Z~kO-N_>-fvr%wP=zg|eGmY;!H8Ur^`@H@_#@uJKmKUad{#u?<b>j5&jEF0- z9*a%Bwnqhv{;Hb$YyW<)#dF0kp3=F$^yi_3EggoxS@u*Pes|zB!$pUef*krfN@ruH zBrddeHa~rEQLfOdV<8tl?@5w4ShdQc(}wHA?E0^ddZ+cAuWnzO?iMipmBlL8%(-&g zo5k|kZ_i8(?l;-1(G~8rYJ=QNi#q`Zm#<9WteFyVeOBp%AG6I~O!=R+uJSdbz@<H` zuB<urHw7%#lx*S3y1DAeynXfl6K$eaB|dn?+J3+AIkSm&_kFLq6SW&7_b4le2VM`X zY+cIm>AIm&ecAEjcC$~<Sngy0<H3Eyvw!c*=aqdUC0^3J>rHH1W{${peI+FiGpkfC zWx-umq9<NX_|S8~#(dum5zU-)mY-{87~B)MTVd&Mwa#61oA13!!#Bq3bR6Foq^c<T z|Ia$wpW^>5^ySW}Z-1;woO|bXlFOOP$!}^W*7&T^_xXOfDCB?r+4VV3k3>%7PyZ%% zjPK)T)%`E>e5Is2L&O{ztM~qVu{%1U$KYC@!mEs^wE1bJ0%vVsnVf6*H^E*_{c1;6 z^y0~Jr{-(zd3tf7bhp1`WX-mz&HIX1{X5iF^|M4#LwSzr>g_t&Q|_NXV({<E`}zMr z*h}i|`w!cgl9<btI@y+!gVB7u9cYaiqtW(@`YcU8)2&rmM7F2buvjrM8(ErfUsA_1 zg@wt&c)C?HOD>y*DQJP&WJX>o76l_qv+bvwS-6y^pVMdIo&1|gXFA_<mT(RW=-M>U zA~f0QUCUYgpzF`-O)ODPpPw3hx-Zy}=k90Ge~r?Q!sUccF)>cP!CTTEQL?N!-MO`m zcYpun*-PeHZ*W*U^~QC(x+fCz7!Tg&SDf_M)+WZsdWKD@rO#i<ITf7?S-)tfO}9DZ z^k!3w)ZcHHTUqS>v{+ZvoA@g3meDv+$m;XNMcAc&c~0Bs?dA83tNhc2wG2aIxsO); z+N5y%sQoM<pGB5)eVn#$J)l(a*ttLT;yUGf98=#PKfhstm8iD(<kM5CS(n`77Tvfy z?|SQoMUzCnT+M1o>d5jqHQQHb_9?ZZS9Y&Eo&;n`zD>?>WWL0;CYCF-{I}4rzjwHf zoZ%~$)()2XI=6n^%31Xi85tAn?nZrip0za7RQKw~i?#imBwuI#Dv<ZF-T5@_JqxFp z+jOCG*EN?czPIIkuf>O`-y0v+*n~A%m?c^Ia_*Cfd$oUFoUn65ESFW~7S-bh-PsE! zx6PS+=)LyOMcqCHj1yBHpT7Kwd7l*T`kHwRy9y&5Ph@^gm##Q+s{a0KH<i*E37itL zg1mXldvC8#tPXw=du@vJ-K#J3Hmpz=pK`4DnBAqzW<k8$*UWnS^xu}Rjqm;!Z!kBV z@bsC;cUOb_J?fb$cYA;68yw@>_M%pQZp73lO4~MEnZCj(&TX=h<-MDqk~UrwlRD;i zRrc~FvHH8$zpnrM?yS5p1LJ<K)alMOEJBQyptXpq)8*2*`0MwAHe;Ld-2JZohxze~ zzYJ_~BDqW@*T22%(pxrjW{#A|%VqoHyM$J@+wz}x?LM+&n@*reX@$o{<J#FzR36Nq zv#05Q(yu?F^Xv|OT^ck0$Isg)(usQxGd>MFX31eGJ7wPD!ptx2{qa+0*8Rx-Rr%{5 z@4Po6&tFWLeQ(`lo-8x7dY`&&Qc~yk%eGz!;wWwN*1fkheYeK^e_c-%Oz(7X+gR+d zDX}{E)JvK#rYGmuiql0Ko<^Q}7IgZ3zP&GdW_q?mtmLy5Nf#X=6?>w1?%b-W4lwcz zO`E8+waI~5NY`VN)pp4lXJ^&S41QU>bmA>tPu`lB1p=Kcte3TVz3x8j)vo`Z9unYY zd%7d-s@8<M<yNBK%2j+1c@&=Y7I}VU)eg~lPDb;8E$(5GzAgOy;!%T_>yPc+I-@t_ zvg2t>mXo_TYk2=^E6iKzA*XkL@f`X4LJn!X2WI7RT-Kle>-qluYZ?=`%x&J>v)E>5 z$r>51REbv-Z@&1m&Y!<G?DF-KGZ>zA->Luf{EPO14Uc~A3-ixfn{e%c@}r#^0y-rr zYk6nY_iH9;UpXSV;O6NN!+gfY{BLrdb0;6)V|q#9SM4t6bfcT{r@qN3CCS`d#<-T@ zA}7Ox+q`#9-`?TBTC`#Ao?cPwj~>M$Jh5FIyDcUyo4loPdiTbqGZypC-MG|yw)Z*4 zl8HAC9jR|R+jA}V@B3eCz5ey<rg@*4`m<3b@$=4@lLvMBFYmDUVkUCh=E3w6tK)90 zKWFn-yXrB=$>_|!ex2UmGdP^Im#FN|h~gI#UG1~tWX48TyTpsfO$$Pnc4nt{cilO0 zAXQbGlhx5kut9$|_qT%Tw-1f>UXuN`G4(B<>$43_z3a55)n9uMmYpDZb4mtVbxwpW z|3}v!TW2%m^{p*qjXoz^J^RCz)W(do#vR-2xIM~}Cp_J^EunR4#zn5*uZ@zYn_lZ( z)ommG#`3??w>!LVI=#2geX`o$A&7PA+*^WWCzOpjzQ0*EWv%;j1J_-%cCF61^k<u4 z=E0i7Iy+CtZ9n0)C*ihDeSvgD^7Y)Oyibi;S1nsH`?qx8-Q8Os|5)}!Cbgk}TW77v zQLP8UH$QcLt*&7BwOf2@lFsLS7oSD{zu>qj{EE&7Qv<&Ht$R1IB?*Tu+!5O*xIIg% z_45wK*=9VqXScRrdU`4+<(8_5_~LiGQw)Wdt$qD-+X<~bw<Tu<q!g#zp8nFbK2c`T z_KTVqo9&tm7Mm_RwqvhX;;Rn^@*Q>84)sksarol5`O^2?Bs#7MWhu@IQ!6vs|LWC> zr3V8xv2k56T`{{U#8Gj{hVotfCg=IWPyC%<-1;hi(RHr0vNJ8;g_~X!FpDi|{d~b( zU;O%^WvAQM@@VoOw@P$7XP<cM@U3ad9e$_k7ao2!fl>72n#{XVz3CK)VY4epGa zcCW;G>G9ma(uizJiGM~tOS_GAcpjwl%*m{lIWB83&G5{<8;$||$qKU!<PH@~)SLD= zWpd%7w^q%U+^)-dJmhZP-oAF@tnY{RAD*%$yQO@>8n*C*EDals3*OAtRX;aLuJ3JH ztHBHp^X&SkcX}V!i>nm-h0Xl1^e+2~<1-@8w3SHYmfv6YSU8M9E`M)9!FzYZT!s@< znU8L}y+*3$$BXEV3TKwB@(X-rtQlq`wx-vidEb<SbNw8a-TU-F=sEu-CNpK%s*|mq zbAC(Mc(*WGOP#jR<$1BiWoPb-n}Ms%Ztk1-E@eqp_V)DKOX^QmsGrxG{La#Ts^MEp z#q)7r6WJB_E_k|XlWIjF%L$_yqMxj%U2|nrdZf3_c-1i$<5|7Q9(h+H%dG?#e_MFL z+vMu%FV`mCD5`p*W%=N>NRrMpmP;#R1FR;iwyFJiHjR7o@dfkNCd!&WG?6(ZE&Bbl zz--pF4u4K2S(o+fK7Y@^tv>(AZktTi{cA4=yKnDWeEq>6ug3p&pX#=r{yjh8M7m_i zL7z)ouFB*ddeh)_`<CSO?DB%*sXR=3qddbD!w<Dh*_kxwe8>A0zs!za+P3fWzgg4y ziuf`sXPd6q_$HVuDIgc2+$G+>&Z1y>bjyyTZb|OnCaUt>i*va#b!%K)nB^bwdc&Ks zOBX%S4l3QYRDA2(^PkiTi*hFXpDe!YS9MBFsD90Ub0;IFZ&K+2PcLk|fBed+ZC8IT zv9&YH{8;os(Wd!EaHE!Zw#%IxjyqiB<aRk;sHnQv+iSeJ|5cl`^miqL&j)x9&j=0t zIL)^wKRa@*FHd-&>QlYSsx|X>JlfY*sU}q4yrf!gzvj*D|Bif~@aK$g?1X({d*#>6 zIBl2apIGb>QnJBW^-}Jx!dDlU@mLC1{>@0d!(+g>=0}O%9%1qP#<MFU`40y!4-P)` zs3|L)ec7%AJ)7`b%l@p2f4Dn)_I8_}ng^~uo+BZ1N$TE_|8vaO@)Reu$9-H@XP^Jg zcu%wW>Gt~cO&PkG%J*k#o$r`c9lUC9`;#>vH-9&`;IsGK&opyE|Kg}CzjBssH2wSb ze3hkkVDdMCGliw=?);s}wLsDAS1;#5H_>fpA2ltSa&kk`LM_+0y;slX1xZbBj6HlL z%UEnqSFfpLkLS-zE}b{#Oyd4KgWI_Nb8XY=sAFrBowEPg_SDA}9f-N-<Yunz_I$-@ z)tWPnx6i6}Y`NH4+uApGdg-2BA9WAj$ZK8QbvsO~;Bob>!(JPNj{n*$_(pg3_MRH& z*!6GCv$B>bl|}D=G3os=2cvybf{zN%TJcVn-||6v_H@xb-f=l?&m)tzcCqVBe7b(} z{OJ1*)lXx-?{4nZcbI5@y*_`|0ozoj?dMstzW((pjbHV_(<@KDKep2DpNgoF`NTVM zyXTZ9|BaM6-QP9k^^%7<&;I2oSA9CK^=H}Hzpu7)Sv21(Grst*$@`lAihXR4f7Sgx z8?nm9w4!k3WBV^F)VS5QowvL^?diJYoMrp%>%Z$inx0sX-2R>JTf@T6WM~Fzk*L%g z8k?i6^t>B&GEdoDVDEG7U+l9KvmY`^)Phcm$rZZaBxAD8WV*z`CzI~`7ru49bi(LL zuIh};j+DuvN5j{5<y^XZ{ai`ZznSM6|6ITOdG!^!r$2+{9S{3|OY_o`9I1dVHI4PY z*O^y2nDzbp_dI;TyW{(J`QNSQ_YZokw<6(SnScAaihv{gin{`(;_B)zNcCR(pv2E} z?wA$-gGcM-s+xl?RIiMvoKte^WU_a(l*NRZUmE{jC|)UTT$%9W@yqG!%-75~67x8; zAwY-W+N=Zx%_6;T35!A&9DH#@i^)@ElSk5-T$NihJ~{8c^gO_Qk?bPLmrH${SJitM z8l0P?P&VCs+m!f8jcN;w6c2ViSY5k2F!WXaY7=GUuZsSG`;UZWtPT1sX!j$=jq})^ z?Ai?s!u5tP-PWFPxV|{>-t1>{8F$=zJ$)B%SY_rfy^}v*Y6`h56!6~o{qFky&%f@+ z_jNj+I2D~|bLv!Y?lIMcy=@XU<^Ox06rWNosb4QUQT&Hf_Rr0~eKRchdTib+Kdcs> z^T|y_O4g(CpykKU`G%J>L~pcbET6IZM$(qaqM_`Zsk)&P44iElySgSTCCq=F<9DKL zLC><*oCgb+AKKo0c1dUIBQD#(XSc4ZEcGd~c~mPXxGF@CN2&9~lQ+w(rv+_~m76M> z_SETM^7+d8^tly%GMc9|*BvT3$-2I&g++aq!M4=*vdpT_R<sB=ce%`P{g`4FXzk%3 zxL}3;x>X)Ea(512@oqgJdU=xmg@#WOp2C$ck1sGV?9e_V?jTh4KxT>JB|rT=v*);2 zMlRWQ$@c6rOMi<c;xk_c%sFGac1p;#qH_!P+)%$ODb{J5uU5Z-$JVar?xdN$M>B=a zE3Jr_rgo^a^kTu3IciKB4@6fwu)cD3R6Si;@7Ege#A#uk-<sCsL%L$@zAK*wY-IV? z6SefM&DJ?nR5_crYqh*8NS-96^mM6j|H4cqnZl<<H_!J4$M#%adGGnsoar09xTop9 z&-un}!>_i!?9-1M)BYX~tDjVOIpj<9@-54+Nhu0dTv9rjmm&FL>7GZY8|}E`6Q4RK zd-PxPp4ZCy^|Vu!v&yAO`_8jW_^8&~?Quloq0Soj`CKc%ir7_%&Hr2S_SEO;k+&n? z1Sf|zCY)QxDw4PE3-dya#R}O+7pZ#ryf05ZU&5PWx-DU1bxiMtwn*mf31;l|GtM*A zaNap)ysPra#gaSqQ>uC8_PJO5Yp=;Un76#)yH`{mV^z!Pr|OmoOmA2gcbW+=Gw^-u zal2uYY{z{2{3L}v{5<8K&$J7-UEHDLwk`3`i51={v1ZzKo93LZ+dq-x*w&zZyiENr zQSx)eX4g%<ap!qemxj(p$%!A-7i-SxSys>M$dTRPpc-c|XU>+t3p}^)xUtsyWze(y zrVY3GSkgr@Y;Gy`{Z9SKbv@Bmf$wE8laD7~!}G~Zxzk0?EoPd%;L!8{t+(HDUp$nE zn7#P<vKJ56UhH1p=Pvn_rTl^14(HeB{`q8CHn#0$H>nUd;mhwAsOJ|<+Wz!J?kDcv z>}$u1>QCL3u`Mq=e(3v#h~K5^|CYsi?_gIrq~;K_G5cWk39a3^FAI0aa@UuH9W*UF z#JjgBh5u)Qd7b>e{~q4aq9wak9S>>ee^PUQajxmoxiZ^mzx0=H`_4Qq{$DmP{=Ma! zle-(8_gQvqSA3b4w13a{OOD63cP6;0%$0g^_tUeNH+<gLuW?$mbmn&J?biOBJBqzx z%6r@$CEw>e$*%WYX!=N4JS5{t=Dod#Rthu97q(Sc&3>d6Z+O4#-%;gbp?|~!eww}i zURj`)^m>hNbo-0y^Y-_Pe0MOF{XA4!IWwPE^O(Wz!nL`d?T`K_`LbQ??b-7GO!wJO zx?mK>)5TY@sMQ;pm{=g6+4VO1^tvb81n+{*><WJtt#aXlQh`lFxnG)i%Ej_sw`cS4 zq))zY9=S$KOm&sKIpZ4zX8*_AG&V)7DVN>0JYL;)<)8WU#3uh=`SSGjKVLNK>yDjQ zulrXzYi^Nu1M82_<(68udnK*a#ZSF@U-u*H&X@YX`j`DL&$oZBZokx$Re0I??Vj$& z*7D0!znSsJ{HqH(m9_MYYW<mAEuY?mZ=SM0tM;;4ec<&!0-w@PPCoZjxPV7&onN=* zvt9ctYpzArocYy%-rg_&*7<v>Wu<c_d!7Joblbe^&(-Ve^WDT(9a|eU^`g&()GoW= zPuu!-?wMWwwf^xri|20Ixe?sbAB+wtDb1ABSzfZt=X^d>-F}8<tDBMP0;^3Y{;=M@ zHla=-%W8ed_9~ZB&Bbl*pM!NC8Nc75b^6Bq%0AbFQ}&B}+m=;!%O^kVykXY0b6GYY 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 QmWBo<CS0njuKsRZ0LIQ?FaQ7m delta 185410 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%0Dov*5#v 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%0DDp9chbCPO_C-^7YF^r#*+C6)Inw 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 CSr6<0 -- GitLab